Agent skill

Nitrosend Webhooks Skill

Receive and verify Nitrosend webhooks, which follow the Standard Webhooks specification. Use when setting up a Nitrosend webhook endpoint, getting the whsec_ signing secret, debugging webhook-id / webhook-timestamp / webhook-signature verification, or handling events like email.delivered, email.bounced, email.complained, email.received, campaign.completed and sending.limit_reached.

Install this skill

npx skills add hookdeck/webhook-skills --skill nitrosend-webhooks


Nitrosend is a transactional and marketing email platform with an agent inbox. It POSTs a signed JSON event to each endpoint you register (up to 10 per brand) when a transactional email is sent, delivered, bounced, complained about, opened, clicked or fails, when a reply reaches one of your inboxes, and when account state changes (campaigns, sending limits, imports, domains). Signatures follow Standard Webhooks, so each delivery carries webhook-id, webhook-timestamp and webhook-signature headers.

When to Use This Skill

  • How do I receive Nitrosend webhooks?
  • How do I verify a Nitrosend webhook signature (webhook-signature)?
  • Where do I find my Nitrosend webhook secret (whsec_...)?
  • How do I handle email.delivered, email.bounced, email.complained or email.received events?
  • How do I react to sending.limit_reached, domain.verification_lost or webhook.disabled?
  • Why is my Nitrosend webhook verification failing, or failing only on retries and replays?

Verification (core)

HeaderValue
webhook-idevt_...; retries reuse it. Your idempotency key
webhook-timestampUnix seconds
webhook-signatureOne or more space-separated v1,<base64> entries

Signed content is {webhook-id}.{webhook-timestamp}.{raw body}, HMAC-SHA256, base64 output. The key is the base64-decoded part of the whsec_... secret after the prefix. Compare against every v1, entry; ignore other versions. If webhook-id is absent, read svix-id, svix-timestamp and svix-signature instead, as Hookdeck does. The examples use the standardwebhooks library (npm and PyPI):

const { Webhook } = require('standardwebhooks');

// rawBody: exact request body (Buffer or string), NOT re-serialised JSON
function verifyNitrosend(rawBody, headers, secret = process.env.NITROSEND_WEBHOOK_SECRET) {
  const p = headers['webhook-id'] ? 'webhook' : 'svix'; // fall back to the svix-* names
  new Webhook(secret).verify(rawBody, {
    'webhook-id': headers[`${p}-id`],
    'webhook-timestamp': headers[`${p}-timestamp`],
    'webhook-signature': headers[`${p}-signature`],
  }, { jsonParse: false }); // throws on a bad signature or stale timestamp → respond 401
}

The library always enforces a fixed 5-minute timestamp window, which is also what Nitrosend tells you to apply. Hookdeck retries and replays resend the original webhook-timestamp, so the library rejects them once they are more than 5 minutes old. If you receive through Hookdeck and need late retries to pass, use the manual verifier in references/verification.md, which can drop or widen the window. In Express, read the body with express.raw({ type: '*/*' }) so any content type verifies, and parse JSON only after the check.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Event Type and Payload

Every body is { type, timestamp, data }. The event name is the top-level type (dotted, such as email.bounced), timestamp is when the event happened, and the event fields sit under data:

{
  "type": "email.bounced",
  "timestamp": "2026-09-30T10:00:00.000Z",
  "data": {
    "message_id": 48213,
    "to": "ada@example.com",
    "subject": "Reset your password",
    "idempotency_key": "reset-8841",
    "tags": { "user_id": "42" },
    "bounce": { "type": "hard", "subtype": "NoEmail" }
  }
}

data.bounce.type (hard) is the bounce kind, not the event type. Test events sent from the dashboard or POST /v1/my/webhooks/{id}/test carry "test": true in data with null ids.

Common Event Types

EventSent when
email.sentThe provider accepted the message
email.deliveredThe recipient's mail server accepted it
email.bouncedIt bounced, hard or soft (data.bounce)
email.complainedThe recipient marked it as spam
email.openedFirst open (scanners and bots don't count)
email.clickedFirst human click of each link
email.failedIt failed for good
email.receivedA reply, automatic reply or new mail reached one of the brand's inboxes
campaign.completed / campaign.failed / campaign.stoppedA campaign finished, ended before reaching anyone, or was paused for a high bounce or complaint rate
flow.pausedA flow was paused for a high bounce or complaint rate
sending.pausedAll sending on the account was paused
sending.limit_approaching / sending.limit_reachedMost of, or all of, the rolling 24-hour sending allowance is used
import.completed / import.failedA contact import finished or failed
domain.verified / domain.verification_lost / domain.dns_degradedA sending domain passed verification, lost it, or its DNS could not be confirmed
webhook.disabledNitrosend turned off a webhook after 3 days of failures

These 21 are the documented events; the list may not be exhaustive, so ignore types you don't recognise. See references/overview.md.

Delivery Semantics

Return any 2xx within 10 seconds. Another status, a timeout or a redirect (not followed) is a failed attempt, retried after 1 min, 5 min, 30 min, 2 h, 8 h and 24 h, then marked failed. After 3 days of continuous failure the endpoint is turned off, the account owner is emailed and a webhook.disabled event is sent; events that occur while it is off are never sent, even after you re-enable it. Events can repeat and a retry can arrive after later events: deduplicate on webhook-id and order by timestamp. Return 400 for missing headers and 401 for a bad signature.

Environment Variables

NITROSEND_WEBHOOK_SECRET=whsec_xxxxx   # full value returned when the webhook is created; handlers fail closed (500) without it

Local Development

# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 nitrosend --path /webhooks/nitrosend

Register the printed URL under Settings > Webhooks, then choose Send test event to deliver a signed event.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 12, 2026

View on GitHub →