1
Set up the project's portal
Each project has exactly one customer portal. In the console, open the project’s Settings → Customer portal, switch it on, give it a name, and pick which of the project’s datasets it exposes. Those datasets are the boundary of what the portal reaches: customers’ destinations receive them and nothing else.The boundary is checked on every event, against the portal as it is now. Stop exposing a dataset and it stops arriving at every customer destination, including ones created before the change; a delivery already queued for it is cancelled rather than sent. A portal that exposes no datasets delivers nothing, and customers cannot add a destination to it. A portal that is switched off delivers nothing and refuses its tokens.Optionally, set your branding and the sites allowed to embed the portal.
2
Issue a customer token
In the same pane, under Access tokens, issue one per customer using your own ID for them, and optionally a customer name. You get a one-time link:The token (
hpt_…) is shown once and stored hashed. It sits in the URL fragment (after #), which a browser never sends to a server, so it stays out of request logs. Tokens issued from the console expire after 30 days unless you change that when issuing; revoke from the same list at any time. Issuing and revoking a token are both recorded in Settings → Audit log.With a customer name (up to 80 characters), the portal says Signed in as . Without one it names nobody. It never shows your customer ID.Links issued before this change carry ?token=… instead. They still open the portal.3
Share or embed
Send the link directly, or embed the portal in your own app.On first load the portal removes the token from the address bar and trades it for a session: an HttpOnly cookie that lasts 8 hours, or until the token expires if that is sooner. The token is not stored in the browser. Revoking the token ends its sessions at once. Sign out clears the session. If the browser blocks the cookie (for example, third-party cookies are blocked in an embedded frame), the portal keeps the token in memory for that page only.
What your customers can do
Everything is scoped to the customer’s token: your workspace, the project, the portal and their customer ID.- See what they will receive. Each exposed dataset is listed with an example payload: the envelope Hookie sends, around the shape of that dataset’s latest record with every value replaced (strings become
"<string>", numbers0, booleansfalse). A portal’s customers share its datasets, so a real value from another customer’s event never appears, only field names. - Verify signatures. A guide to checking either signature every delivery carries (see Verifying deliveries below).
- Manage their endpoints. Add one for chosen event types (HTTPS only, within the exposed datasets), edit its name, URL and event types, pause and resume it, and delete it. The event types must be a non-empty subset of what the portal exposes; an empty list is refused rather than read as “all”.
- Manage signing secrets. Show an endpoint’s signing secret again, and rotate it. The old secret keeps signing alongside the new one for 24 hours so their receiver can move over, and they can end that overlap early.
- Read their own data in tables you design. Any saved view you share appears as a read-only Your data table, with a picker when you share more than one, paged 25 rows at a time. They see only the columns you chose and only the rows that belong to them.
- Inspect deliveries. The delivery log shows the destination, status, response code, error, the next retry and a preview of the first 300 characters of what their endpoint answered. Open a delivery for its full response, endpoint URL and content type.
- Replay a delivery. A replay is a new delivery, charged to your monthly delivery allowance like a replay from the console. It is refused, not held, when the allowance is used up.
portal|{customer_id} as the actor.
Share a table with your customers
A saved view of a dataset (see Datasets and records) can be shared in the portal as part of your service: an order history, a list of tickets, a usage report.- Open the dataset in the console, then create or edit a view. Pick the columns, their headings and the sort as usual.
- Tick Share in the customer portal.
- Save. Customers see the table the next time they open the portal. Clear the checkbox to stop sharing; the view stays in your console.
portal_shared: true when you create or update a record view (agents can pass the same field to create_record_view). Customers read the tables with GET /portal/api/views and GET /portal/api/views/{id}/records, which take the same portal token or session as the rest of the portal API.
Verifying deliveries
The portal’s Example payloads section ends with a verification guide. Every delivery is signed twice with the endpoint’s signing secret (whsec_…), both over the same timestamp, so your customers can check either signature.
Option 1: Hookie-Signature
TheHookie-Signature header looks like t=<unix seconds>,v1=<hex HMAC-SHA256>. Compute an HMAC-SHA256 of <t>.<raw request body>, using the whole whsec_… secret as the key. Accept the request when any v1 matches and t is recent. During a secret rotation the header carries one v1 for each live secret, for 24 hours. The portal shows a Node.js snippet, and Verify signatures has more languages.
Option 2: Standard Webhooks
Every delivery also carries the Standard Webhooks headerswebhook-id, webhook-timestamp and webhook-signature. Rather than writing the check themselves, your customers can use an official standardwebhooks library. There are libraries for JavaScript, Python, Go, Ruby, PHP, Java, Rust, C# and more.
Pass the whsec_ signing secret to the library exactly as shown. Don’t strip the prefix or base64-decode it; the library does both.
- Signature format.
webhook-signatureisv1,<base64 HMAC-SHA256>over<webhook-id>.<webhook-timestamp>.<raw request body>. During a rotation it carries one space-separatedv1,value for each live secret. - Retries.
webhook-idis the delivery id, so a retry repeats it. To ignore an event you’ve already processed, deduplicate onHookie-Event-Id.
GET /portal/api/examples returns both schemes: signature for Hookie-Signature, and standard_webhooks for the Standard Webhooks set.
Branding
Under Settings → Customer portal, set:- Logo URL: an
httpsimage URL, shown in the portal’s header. - Primary colour: a
#rrggbbcolour for the portal’s buttons and focus ring. Button labels switch between black and white, whichever stays readable on it.
400. There is no free-form CSS.
Embed it in your app
Browsers show the portal inside another site’siframe only when that site is on the portal’s allowlist. The portal sends Content-Security-Policy: frame-ancestors with exactly that list, and 'none' when the list is empty.
1
Allow your site
Under Settings → Customer portal → Sites allowed to embed the portal, enter one origin per line, up to 10, such as
https://app.example.com. Each must be an exact https origin: no path and no wildcard.2
Add the frame
Copy the snippet from Embed the portal in the same pane. It looks like this:Issue
ACCESS_TOKEN from your server (POST /admin/api/projects/{project_id}/portals/{id}/tokens) for the signed-in customer. Never put a token in page source.
The messages carry only a type and a height, never a token or event data. The session cookie is
SameSite=None; Partitioned, so it works inside your frame.
Offboard a customer
Two ways to cut a customer off, both under Access tokens:- Revoke a token. If it was the customer’s last live token on the portal, their destinations are switched off in the same step.
- Offboard customer revokes every token they hold and switches off every destination they created, at once. It asks you to confirm first, and is recorded in the audit log as
offboard_portal_customer.
GET /admin/api/projects/{project_id}/destinations returns portal_id, customer_id and customer_name for them.
Remove a portal
Switching a portal off is reversible: its tokens stop working and it delivers nothing, but everything comes back when you switch it on. To be done with a portal for good, open Settings → Customer portal → Remove portal and type the portal’s name to confirm. Removing it:- revokes every access link at once;
- cancels the deliveries still waiting to go to its customers’ destinations (queued, retrying or paused), and sends those destinations nothing more;
- keeps the destinations and their delivery history, and deletes nothing;
- is recorded in Settings → Audit log as
remove_portal, with how many tokens, destinations and deliveries it cut off.
DELETE /admin/api/projects/{project_id}/portal with {"confirm": "<the portal's name>"}. The name must match exactly, so a stray DELETE cannot remove a portal; without it the answer is 400. A project with no portal, or one already removed, answers 404. To switch a portal off instead, which you can undo, update it with enabled: false (or hookie portals delete, which only switches it off).
Portal API
Everything the portal UI does is also available as a small token-authed API:PUT is accepted as well as PATCH on a destination. The token is not accepted in the query string (?token=…): a token there ends up in request logs. Each token can make about 60 calls a minute. That is a best-effort guard, not an exact count: each Cloudflare location counts separately, so a short burst can get through above it. Over it, the API answers 429 with Retry-After: 60. Writes made with the session cookie, rather than a bearer token, must send X-Requested-With: fetch.
Why one portal per project: a project is one customer’s space. Two portals over the same project would show the same events to two audiences, so Hookie keeps it to one — creating a second is refused. Workspaces that had several portals in one project kept the most recently updated; the others were retired (tokens revoked, their destinations paused, nothing deleted), and each retirement is listed in Settings → Audit log.
Scoping guarantees
Deliveries made to customer destinations behave exactly like every other Hookie delivery: HMAC-signed, retried with backoff, dead-lettered, and verifiable. Sign in and open a project’s Settings → Customer portal to try it.