Skip to main content
This walks one event all the way through Hookie: into an endpoint, into a dataset as a record, and out to a destination as a signed delivery. Everything here works on the Free plan.

1. Create a project

Create an account with Email me a code (a 6-digit code, valid for 10 minutes) or Continue with GitHub. There is no password. Your first sign-in creates your workspace, with an empty project called Default. Open Projects in the side navigation and choose Create a project. Give it a name, such as Orders, leave Start with on Endpoints — one endpoint, ready to receive events, and choose Create. The project opens on its Endpoints tab. Hookie has made one endpoint for you, named after the project (Orders endpoint), tagged new, and storing what it receives in the dataset default.
Rather use the Default project? Open it, choose Add endpoint on its Endpoints tab, and give the endpoint a name and a dataset. Until your workspace has an endpoint, an event and a destination, Home shows a Get started checklist that links to each step below. Each step is ticked off from what the workspace really holds, not from what you clicked.

2. Copy the endpoint URL

The endpoint’s card shows its public URL, with a copy button:
The last segment is a long random value that Hookie generates, and it is the endpoint’s only credential: anyone who has the URL can send events to it. Paste it into a provider’s webhook settings, a form’s action, or your own code. If it leaks, Rotate URL… in the card’s menu gives the endpoint a new URL and keeps the old one working for an overlap you choose. Show curl on the card gives you a ready-to-paste command, and so does the project’s Overview tab, under Send events to this project.

3. Send a test event

Choose Send test event on the endpoint’s card. It is a button under the URL, next to View records and Show curl, not an item in the card’s menu. Owners, admins and developers see it, and it is disabled while the endpoint is disabled. The dialog holds a sample JSON payload under Event payload (JSON). Edit it if you like, then choose Send test event. The dialog posts to the endpoint’s real URL, so the event is stored, routed and fanned out exactly like one from a provider, and it counts against your monthly event quota like one. It then shows three stages:
  • Sending, then Waiting for acknowledgement, with the status and the time taken, such as 201 · 143 ms.
  • Fan-out to destinations, which follows the event for up to 30 seconds. With no destination yet, it says the event was stored and that no enabled destination accepts its dataset, and it offers Add a destination to forward events.
Follow this event in Observability opens the event’s whole chain. You can send the same kind of event from a terminal:
submission_id names the raw body Hookie stored, and routed lists the datasets the event landed in.

4. See the record

Choose See the record in default in the dialog, or View records on the endpoint’s card. Both open the endpoint’s dataset on the project’s Datasets tab, newest record first. Open a record with its eye icon (Open record) to see all of it, and the Request it arrived in: the method, content type, source IP, query string and headers, such as X-GitHub-Event. Credentials such as Authorization and Cookie are stored as [redacted]. A record is what Hookie routed into a dataset. The body exactly as it arrived is kept as well, as a submission: find it on the Observability tab under Submissions. Export CSV and Export XLSX on the dataset export all of it, not just the page you see. See Datasets and records.

5. Add a destination

A destination is an https URL of yours that Hookie sends each new record to, signed. For this step, any https URL that answers with a 2xx status will do, such as a request inspector or a route on your own server. Open the project’s Connections tab. Under Destinations · outbound, choose Add destination and fill in:
  • Name, such as My server;
  • Type: leave HTTPS webhook selected. A destination can also post to Slack or write to an S3-compatible bucket;
  • URL, which must be https;
  • Dataset filter (optional): leave it empty to receive every dataset.
Choose Create. Hookie shows the destination’s signing secret (whsec_…) under Signing secret — shown once. Copy it: your receiver uses it to check that a request came from Hookie. Owners and admins can see it again later with Reveal secret on the destination’s tile. The Free plan runs one destination. See Limits and plans.

6. Watch the delivery arrive

Go back to the Endpoints tab and choose Send test event again. This time Fan-out to destinations ends with Delivered to My server (200). If your URL refused the request or could not be reached, the dialog says so, and whether Hookie will try again. Now open the project’s Deliveries tab. Each row is one delivery: one event sent to one destination. The eye icon (Details) lists every attempt and the latest response body your URL sent back. A delivery that failed for good has a Replay button. Every delivery is signed twice with the destination’s secret: as Hookie-Signature, and as the Standard Webhooks headers. It also carries Hookie-Event-Id, which stays the same on every retry and replay of that event. Verify the signature before you trust a request, and dedupe on the event id, because delivery is at-least-once. Destinations and deliveries covers retries, holds and replays.

Trace an event later

The Observability tab searches Events, Submissions, Deliveries, AI runs and AI calls over the last 24 hours by default. Widen it with Time range, down to a custom window to the minute. Each kind has its own filters, such as a delivery’s status, destination and response code, or a submission’s routing status. Trace an id takes any record, submission, delivery, AI run or workflow run id, such as the Hookie-Delivery-Id your receiver logged, and shows the chain it belongs to. Switch on Live to watch events arrive as you send them. The filters are kept in the page address, so a search can be shared as a link. When a provider says Hookie returned an error, choose Activity in the endpoint’s menu. It lists what the endpoint received and refused over the last 7 days, and why each request was refused: IP allowlist, rate limit, quota, disabled endpoint, content type, signature and more. Refused requests never count against your quota.

Point an HTML form at it

An endpoint accepts what a browser actually posts, so a plain form needs no JavaScript and no backend of your own. Set action to your endpoint URL:
The fields arrive as an ordinary record, stored whole in the endpoint’s dataset:
Both form encodings work: application/x-www-form-urlencoded and multipart/form-data. A few details worth knowing:
  • A field that repeats (checkboxes, a multi-select) becomes an array rather than losing all but one value.
  • Field names are kept verbatim and flat. A field called user.email stays the key user.email and does not become a nested object, so a mapping sees exactly what the form sent.
  • Every form value arrives as text. gt, gte, lt and lte still compare a field such as amount=150 as the number 150. In workflows and triggers equals and in compare exactly, so match "150" there, not 150; rules and endpoint criteria compare as text, so either works.
  • A file part is recorded as a descriptor and its bytes are discarded, in the record and in the raw submission. Hookie stores records, not files, so you get proof something was attached without the contents:
  • Everything else is still parsed as JSON regardless of its Content-Type, which is what most webhook providers send. A body that is not JSON but is sent as text, such as text/plain, XML, CSV or NDJSON, is stored as { "body": "<the text>", "content_type": "text/plain" }. See the API reference.

Send visitors to a thank-you page

Because the browser navigates to the endpoint on submit, a visitor lands on Hookie’s own thank-you page, with a link back to your form. To send them to your page instead, give the endpoint a redirect URL: on the Endpoints tab, choose Edit… in the endpoint’s menu and set Redirect after a form submit to your page (it must be https://). A native form post is then answered 303 See Other to that page once the submission is stored. fetch() calls and server senders still get the JSON, and a resubmitted form that was already stored is sent to the same page.

Submit with fetch()

To keep the visitor on your page, submit with fetch(). A browser only lets a page read the answer from an origin that allows it, so add your site under Allowed browser origins in the same form: one origin per line, such as https://example.com, or * for any page. Nothing is allowed by default. For an allowed origin the endpoint answers the OPTIONS preflight that a JSON body triggers, and every response carries Access-Control-Allow-Origin, so your page can show the error on a 429 too.
A FormData body needs no preflight, because it is a form encoding. Without an allowed origin the submission is still stored, but your page cannot read the answer, and fetch() rejects with “Failed to fetch”.
hookie forms init contact sets all of this up in one command, with spam protection and field checks. See Forms.

Safe retries

A retry returns the original submission (200, with "idempotent": true) instead of creating a duplicate, and it never counts twice against your quota. Hookie recognises the delivery ids that webhook providers already send and keep across their retries, so Stripe, GitHub, Shopify, GitLab, Twilio, Linear, Twitch, Atlassian, Slack, Svix and every Standard Webhooks sender are deduplicated with nothing to configure. deduplicated_by in the answer says which id matched. Duplicates are tracked per endpoint, so two endpoints never collide. From your own code, send an Idempotency-Key:

Send from your own servers

A server can send with an ingest key (ik_live_…) instead of an endpoint URL. Create one on the project’s Ingest keys tab: it is shown once, stored hashed, and can carry an expiry and an IP allowlist.
With a dataset at the end of the path, as here, the payload is stored whole in that dataset. Leave the dataset out and the project’s rules decide where it goes, and in what shape. An event no rule matches is stored whole in the key’s default dataset (default unless you chose another when you created the key), and the response says fallback: true.
Rules run only on POST /v1/ingest/{ingest_key} with no dataset in the path. An endpoint URL always writes to the endpoint’s own dataset and never consults rules, so adding a rule and then posting to an endpoint URL changes nothing.
See Endpoints and ingest keys and Rules and mappings.

Next