Webhooks let Pulsewave push events to your server as they happen, instead of you polling Events or Messages.
Setting up an endpoint
Create a webhook endpoint pointing at a URL on your server, and choose which events to receive:
curl -X POST https://api.pulsewave.dev/v1/webhook-endpoints \
-H "Authorization: Bearer pw_live_8f2k9q3m1n7r5t6y4u2i0o8p" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/pulsewave",
"enabled_events": ["message.delivered", "message.bounced", "contact.unsubscribed"]
}'
The response includes a secret starting with whsec_. Save it — you’ll need it to verify incoming payloads, and it’s only shown once.
Event types
| Event | Fired when |
|---|
message.sent | A message is handed off to the carrier |
message.delivered | The receiving server confirms delivery |
message.bounced | A message permanently fails to deliver |
message.opened | An email recipient opens the message |
message.clicked | A recipient clicks a tracked link |
message.complained | A recipient marks the message as spam |
contact.unsubscribed | A contact unsubscribes |
domain.verified | A sending domain passes DNS verification |
Payload shape
Every event has the same envelope:
{
"id": "evt_1a2b3c",
"object": "event",
"type": "message.delivered",
"created_at": "2024-06-01T14:32:00Z",
"data": {
"id": "msg_3p2k9q",
"object": "message",
"channel": "email",
"status": "delivered",
"to": "ada@example.com"
}
}
Verifying signatures
Every request includes a Pulsewave-Signature header. Verify it before trusting the payload, so an attacker can’t spoof events by POSTing to your endpoint directly.
import crypto from 'node:crypto';
function verifySignature(rawBody, signatureHeader, secret) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}
Compute the signature over the raw request body, before your framework parses it as JSON. Re-serializing the parsed body can change whitespace and break the comparison.
Retries
If your endpoint doesn’t return a 2xx within 10 seconds, Pulsewave retries with exponential backoff for up to 24 hours: after 1 minute, 5 minutes, 30 minutes, 2 hours, then every 6 hours. Return 200 as soon as you’ve durably queued the event — do the slow work asynchronously.
Disabling an endpoint
If an endpoint fails repeatedly, Pulsewave automatically sets its status to disabled after 24 hours of consecutive failures and stops sending it events. Re-enable it with Update a webhook endpoint once it’s fixed.