Docs
Dashboard

Receive and verify webhooks

Last updated View as Markdown

Receive and verify webhooks

Receive Thor events without performing the same business action twice. Verify the delivery, accept it durably, and process slow work separately from the HTTP request.

Before you begin

Use a reachable HTTPS endpoint, secret storage, and a durable inbox or queue. Select only the event types your integration needs from WebhookEventType. For an embedded app, prefer the template’s authenticate.webhook(request) helper; see App authentication and sessions.

1. Create a subscription

Code in graphql

Replace the URL with your receiver. Store the returned subscription secret securely and associate it with the project and subscription in your receiver configuration. Do not log the entire mutation response because it contains the secret.

2. Verify the raw body

Thor sends a JSON event envelope and a Thor-Signature header shaped as t=<unix-seconds>,v1=<hex-hmac>. The signed payload is the timestamp, a period, and the exact raw body. Use HMAC-SHA256 with the subscription secret and a constant-time comparison. Do not parse and reserialize the body before verification.

This Node example verifies one signature and returns the parsed envelope. Its five-minute tolerance is a receiver policy, not a promised delivery latency:

Verify a webhook
Code in typescript

Read the request body once as bytes, pass the header value and configured secret to this function, and return a non-success response for invalid deliveries. Apply a request-size limit appropriate for your endpoint before buffering the body. Headers such as Thor-Project, Thor-Topic, and Thor-Event-Id are useful routing or diagnostic values; do not treat them as independently authenticated by a signature over the body. Use your configured subscription/project association for authorization decisions.

3. Accept once, process safely

The envelope includes id, object, created, idempotency_key, data, and type. Store its verified idempotency key in a uniquely constrained inbox scoped to your configured project or integration. Atomically accept the event and make its work recoverable, then return a prompt 2xx response. A duplicate that is already durably accepted can be acknowledged without another business action.

Keep processing idempotent as well: a worker can fail after an external action but before marking the inbox complete. Use a stable destination reference and reconcile the external result on retry. Do not assume deliveries arrive in order; read current resource state when applying an older event could overwrite newer data.

4. Inspect and resend

Inspect webhookDelivery when a receiver fails. webhookDeliveryResend resends a delivery while retaining its original idempotency key. It should not create another business action in a correctly deduplicated receiver.

Verify

Test the original event, an identical resend, an altered body, a wrong secret, a stale timestamp, and a worker restart after acceptance. Confirm one durable event and one intended business outcome. Do not use real customer or payment payloads in public examples or test fixtures.