Resend Webhook Integration & SVIX Signature Verification
Resend Webhook Integration & SVIX Signature Verification
Available from: v1.0.25
NurtureHub receives real-time email delivery events from Resend via a webhook endpoint at /api/webhooks/resend. This powers live delivery confirmation, bounce suppression, consent management, and engagement-based intent scoring — without polling or manual reconciliation.
How It Works
When NurtureHub sends an email via Resend, it embeds a set of tags into the message at send time. When Resend fires a delivery event (delivered, bounced, opened, clicked, etc.), those tags travel back with the event payload, allowing the webhook handler to correlate it precisely with the right tenant, contact, journey, and scheduled email record.
The webhook handler then dispatches the event into the Inngest processing pipeline, where the appropriate function handles the update idempotently.
Supported Events
| Resend Event | Inngest Event Fired | Effect |
|---|---|---|
email.delivered | email/delivery.confirmed | scheduledEmail marked sent; deliveredAt recorded; audit log written |
email.bounced | email/bounce.received | Contact email suppressed; journey paused; agent notified |
email.complained | email/bounce.received (bounceType=spam_complaint) | Contact email suppressed; consent set to denied; agent notified |
email.opened | email/engagement.received (eventType=opened) | Engagement event written; intent score recalculation triggered |
email.link.clicked | email/engagement.received (eventType=clicked) | Engagement event written with clicked URL; booking-link detection adds 30pt intent weight |
email.delivery_delayed | (none) | Warning logged; Resend retries automatically |
Booking-Link Detection
When a recipient clicks a link, the handler checks whether the URL matches the pattern /book|valuat|appoint|calendly/i. If it does, a 30-point intent weight bonus is applied during scoring — surfacing the contact as a hot lead faster than a standard click.
NurtureHub Tag Schema
The following tags are embedded in every outbound Resend email and read back on every inbound webhook event:
| Tag Key | Description |
|---|---|
nh_tenant_id | The NurtureHub agency tenant ID |
nh_contact_id | The CRM contact being nurtured |
nh_scheduled_email_id | The specific scheduled send record |
nh_journey_id | The nurture journey this email belongs to |
nh_journey_email_id | The individual journey email step |
Events arriving without these tags are logged and skipped — they do not cause errors or trigger retries.
Signature Verification
Resend delivers webhooks via SVIX. The handler verifies every inbound request using HMAC-SHA256 before processing:
- Algorithm: HMAC-SHA256 over
${svix-id}.${svix-timestamp}.${raw-body} - Runtime: Web Crypto SubtleCrypto API — compatible with Next.js Node.js and Edge runtimes
- Replay protection: Requests with a timestamp older than 5 minutes are rejected
- Multi-signature support: All
v1,<sig>entries in thesvix-signatureheader are checked; verification passes if any entry matches - Dev mode: If
RESEND_WEBHOOK_SECRETis not set, signature verification is skipped with a warning — useful for local development
Required Headers
svix-id: <unique message ID>
svix-timestamp: <unix timestamp in seconds>
svix-signature: v1,<base64-encoded-signature> [v1,<additional-sig> ...]
Requests missing any of these headers when a secret is configured are rejected with 401 Unauthorized.
Configuration
1. Obtain your webhook secret
Go to https://resend.com/webhooks and create a new webhook endpoint pointing at:
https://<your-domain>/api/webhooks/resend
Copy the signing secret (it will be prefixed with whsec_).
2. Set the environment variable
RESEND_WEBHOOK_SECRET=whsec_<your-secret>
3. Select events to subscribe to
Subscribe to all six event types in the Resend dashboard:
email.deliveredemail.bouncedemail.complainedemail.openedemail.link.clickedemail.delivery_delayed
Idempotency
The resendDeliveryConfirmed Inngest function is fully idempotent. If a email/delivery.confirmed event is received more than once for the same record (e.g. due to a Resend retry), the function detects that the record is already in sent or cancelled state and exits cleanly without writing duplicate data.
Error Handling
The webhook endpoint always returns HTTP 200 regardless of internal processing errors. This is intentional — returning 4xx or 5xx would cause Resend to retry the event, potentially creating duplicate processing. All processing errors are logged for observability without triggering retry storms.
Parallel Tracking Paths
Engagement events (opens and clicks) can reach NurtureHub via two parallel paths:
- Pixel / redirect tracking — the existing path using tracking pixels and redirect links embedded in emails.
- Resend webhook — the new path added in v1.0.25.
Both paths feed into the same email-engagement-processor Inngest function. The webhook path uses a synthetic tokenId format (resend_open_<msgId>, resend_click_<msgId>) to distinguish its events from the pixel/redirect path.