Skip to main content
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

EventFired when
message.sentA message is handed off to the carrier
message.deliveredThe receiving server confirms delivery
message.bouncedA message permanently fails to deliver
message.openedAn email recipient opens the message
message.clickedA recipient clicks a tracked link
message.complainedA recipient marks the message as spam
contact.unsubscribedA contact unsubscribes
domain.verifiedA 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.