Dub webhook signature verification: raw bodies and replays in Node.js
Go deeper on this topic
SaaS integrations
Give AI the context to continue the work: source conversations, open tasks, and unresolved details, from adoption decisions to integration code.
Article 3 of 6 in this series.
Next reads
Record Slack huddles and feed AI at 22:00: six Circleback settings
A Circleback workflow for giving AI usable work context: Slack huddles, a 22:00 sync, source statements, open tasks, and adoption criteria. Separates workflow experience, documented features, and local test evidence.
Circleback action items to tasks: testing webhook signatures, owners, and replay keys
Runnable Circleback webhook code tests signatures, owners, SQLite upserts, and stale replays. A separate status example preserves destination-owned DONE. Uses synthetic data.
Track Dub referral links in Japanese and English without breaking referral IDs
Adding UTMs can re-encode existing referral parameters even when their decoded values stay the same. Reproduce spaces, tildes, and duplicate keys locally, with limits for signed URLs and commission attribution.
Use the topic hub as a map, then continue to the next article in the series. New here? Start at the home hub →
Before passing a Dub signup notification into the next task, verify the original request body and extract only the fields that task needs. This guide checks Dub-Signature and projects a lead.created event into a small work record with an offline example.
Sources checked October 22, 2026. We have not tested live Dub delivery, external database storage, or sending data to an AI service. Links to Dub are official links, with no referral ID. For the sending side, see conversion tracking; for building URLs, see UTM tracking.
Decide what the next task needs
Suppose a signup starts a task to review a customer's onboarding. An AI does not need the entire notification. Our example keeps the event ID, type, creation time, event name, and your internal customer ID. It excludes the email address. A customer ID may still identify a person; check the destination's data-use terms before using real records.
Treat this record as evidence of a received signup event, without inferring payment or a confirmed commission. Give AI the source event ID, verified fields, and the next human review task separately. The sample only prints a record; it creates no tasks and sends no notifications.
Dub configuration and signature format
The official introduction, fetched today, requires Business or above. Select a receiver URL and event types in Webhooks settings, then find the signing secret under Configuration. We did not create a webhook. Keep the secret in a server-side environment variable or equivalent, outside the browser, article, and Git.
The official verification guide calculates HMAC-SHA256 over the raw request body using the secret and compares it with the hex Dub-Signature header. Parsing with req.json() and serializing with JSON.stringify() may change whitespace or line breaks. Verify the original body obtained with await req.text().
Our Node.js/Bun example validates a 64-digit hex signature and uses timingSafeEqual on equal-length buffers. This is our local implementation, not an official SDK example.
const rawBody = await req.text()
const signature = req.headers.get('Dub-Signature')
// Import receiveLead from the downloadable file below.
const record = receiveLead(rawBody, signature, process.env.DUB_WEBHOOK_SECRET)This fragment is not a complete HTTP handler. Add body-size limits, map exceptions to HTTP responses, and provide durable storage before production. A success response before persistence can leave a later processing failure unrecovered.
Reproduce raw-body changes without credentials
Save dub-webhook-demo.mjs into an empty folder and run it with Bun or Node.js 22+. It signs synthetic input with a public demonstration key. It uses no network, credentials, or file storage.
bun dub-webhook-demo.mjs
# or
node dub-webhook-demo.mjsBun and Node produced identical output for this synthetic JSON:
| Input | Result |
|---|---|
| Original signed body with line breaks | A work record with eventId evt_demo |
| Append a space to the body | tamperedAccepted: false |
| Parse JSON and serialize without line breaks | reserializedAccepted: false |
| Submit the original body and signature again | replayAccepted: true |
A valid signature is followed by JSON and input checks: malformed JSON, unsupported events, and a missing customer ID are rejected. The official lead.created reference places id, event, createdAt, and data at the top level, with the customer inside data.customer. Our projection only accepts leads with an internal customer ID. It does not validate Dub's complete schema.
Signature checks do not prevent replays
The replayAccepted: true result shows the limit: the same valid body and signature pass again. This example implements neither expiry checks nor persistent deduplication.
The introduction documents retries for non-2XX responses, with exponential backoff capped at 24 hours from the fifth retry. It also documents disabling a webhook after 20 consecutive failures. Do not treat delivery as unconditional and indefinite; inspect failures and responses in Event Logs.
For production, consider a persistent inbox with a unique event ID and a record of downstream task state. Return success after signature checks, input validation, and durable persistence; make subsequent processing resumable. We have not tested a database, queue, concurrent deliveries, event ordering, or live Dub delivery.
What the experiment establishes
Local tests cover valid signatures, tampering, reserialization, missing/malformed signatures, a different key, signed invalid JSON, null, unsupported events, missing required values, and repeat acceptance. Synthetic tests do not establish service delivery guarantees or time savings.
For another receiving workflow, see Circleback webhooks. Find the examples in SaaS Workflows and the starter local demo. Use each product's own signature header and payload contract.
FAQ
- Q. Which header carries a Dub webhook signature?
- The official guide uses Dub-Signature. Calculate HMAC-SHA256 over the raw request body with the signing secret and compare it with the hex header. We have not tested live Dub delivery.
- Q. Can I verify the signature after req.json()?
- Serializing parsed JSON may change whitespace or line breaks and break the signature. Read the original body with req.text(), verify it, then parse JSON. Our synthetic example confirms a reserialization mismatch.
- Q. Which plan supports Dub webhooks?
- The official introduction fetched on October 22, 2026 requires Business or above. Select a receiver URL and events in Webhooks settings and find the signing secret under Configuration. We did not create a webhook.
- Q. Does a valid signature prevent duplicate notifications?
- This example accepts the same valid body and signature again. It has no expiry checks or persistent deduplication. Consider storing the event ID and downstream task state in production. Database and retry control are untested.
- Q. Does the sample use an API key or an AI service?
- No. It signs synthetic data with a public demonstration key and prints selected fields. It uses no network, credentials, file storage, or AI service. Live delivery and business outcomes remain untested.