Docs
One JSON POST per event to your own URL, for the events you tick in Settings, on the Webhooks tab. Every delivery is signed, written down before the first try, and tried again if your server does not take it.
sent
An email left one of your inboxes. Announced within a minute of going out, once per email.
{
"event": "sent",
"email": "owner@shop.example",
"campaign": "Woo speed UK",
"mailbox": "rae@yourdomain.example",
"step": "1",
"subject": "Your checkout on Shop Example",
"sent_at": "2026-09-13T09:14:02.000Z"
}reply
A human answered. Out-of-office and bounces are not replies.
{
"event": "reply",
"domain": "shop.example",
"person": "Rebecca",
"text": "Sounds good, call me Thursday?",
"campaign": "Woo speed UK",
"mailbox": "rae@yourdomain.example",
"reply_id": "0b5e8f7a-2c1d-4e3f-9a8b-7c6d5e4f3a2b",
"link": "https://app.craftnudge.example/unibox?reply=0b5e8f7a-2c1d-4e3f-9a8b-7c6d5e4f3a2b"
}bounce
The address rejected the email.
{
"event": "bounce",
"email": "nobody@shop.example",
"campaign": "Woo speed UK",
"mailbox": "rae@yourdomain.example",
"code": "5.1.1",
"category": "bad_address",
"link": "https://app.craftnudge.example/unibox?reply=0b5e8f7a-2c1d-4e3f-9a8b-7c6d5e4f3a2b"
}unsubscribe
Someone used the unsubscribe link. They are on your blocklist from this moment.
{
"event": "unsubscribe",
"email": "owner@shop.example",
"campaign": "Woo speed UK"
}interested
You marked a reply as interested. Sent each time it is marked, including again.
{
"event": "interested",
"email": "owner@shop.example",
"link": "https://app.craftnudge.example/unibox?reply=0b5e8f7a-2c1d-4e3f-9a8b-7c6d5e4f3a2b"
}interest_changed
You moved a reply to a different status. Not sent when the status did not change.
{
"event": "interest_changed",
"email": "owner@shop.example",
"from": "lead",
"to": "meeting_booked",
"link": "https://app.craftnudge.example/unibox?reply=0b5e8f7a-2c1d-4e3f-9a8b-7c6d5e4f3a2b"
}task_due
A lead reached a manual step, so a task is waiting for someone. Sent once per task.
{
"event": "task_due",
"email": "owner@shop.example",
"campaign": "Woo speed UK",
"kind": "linkedin",
"text": "Connect on LinkedIn and mention the checkout speed."
}Content-Type
application/json
X-CraftNudge-Event
The event name, the same as the event field in the body.
X-CraftNudge-Delivery
The delivery id. A retry carries the same one, so you can tell it is one event.
X-CraftNudge-Timestamp
When this attempt was signed, in seconds since 1970.
X-CraftNudge-Signature
sha256= and the HMAC-SHA256 of the timestamp, a dot, and the raw body, with your signing secret.
An owner can see the signing secret on the Integrations page, and rotate it there. Work out the signature from the timestamp header and the raw body, compare it with the signature header, and refuse a timestamp more than five minutes from your own clock. Then nobody else can post to your URL as us, or replay a delivery older than that. A retry, or a copy sent again inside those five minutes, carries the same delivery id, so keep the ids you have handled and skip one you have seen.
import { createHmac, timingSafeEqual } from 'node:crypto'
// rawBody is the body exactly as it arrived, before any JSON parsing.
function isFromCraftNudge(secret, headers, rawBody) {
const timestamp = headers['x-craftnudge-timestamp']
const expected = Buffer.from('sha256=' + createHmac('sha256', secret).update(timestamp + '.' + rawBody).digest('hex'))
const given = Buffer.from(headers['x-craftnudge-signature'] ?? '')
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300
return fresh && given.length === expected.length && timingSafeEqual(given, expected)
}A delivery that fails is tried again after a minute, five minutes, thirty minutes, two hours and twelve hours, then given up. Each retry carries the same delivery id and body, with a fresh timestamp and signature. The Integrations page lists every delivery and whether it arrived.
With an API key, another system subscribes a URL to one event and gets the same signed, retried deliveries. A sample body for each event helps a Zap get built before any real event has happened. The three routes are in the API reference.
An owner can connect HubSpot or Pipedrive on the Integrations page with a token. Each reply, and each change of interest, is added as a note on the contact with that email, and the contact is made if it is not there. Nothing is read from your CRM.