API Reference

Webhooks

Subscribe to events. We POST a signed JSON payload to your URL within seconds. Retries are automatic with exponential backoff if your endpoint doesn't respond 2xx.

Create a webhook

POST/v1/webhooks

Register a URL to receive event deliveries.

{
"url": "https://your-app.com/webhooks/bluereplies",
"events": ["message.inbound", "message.delivered", "message.read"]
}
FieldTypeDescription
urlrequiredstringHTTPS URL we will POST to. Must respond in < 30s.
eventsrequiredstring[]Array of event types to subscribe to. See list below.
isActivebooleandefault trueDisable temporarily without deleting.

Response includes a signingSecret — store it. You'll use it to verify every delivery.

The delivery payload

Every delivery looks like this:

{
"event": "message.delivered",
"timestamp": "2026-05-25T17:23:15.812Z",
"deliveryId": "wdl_…",
"data": {
"messageId": "msg_pq3z9Vc7BmHkLxQ1aWoZP",
"from": "+12138765278",
"to": "+15551234567",
"lineId": "lin_a802426485c04055f82cafd3",
"deliveredAt": "2026-05-25T17:23:15.812Z"
}
}

Headers we send: x-bluereplies-signature (HMAC-SHA256 hex), x-bluereplies-event (event name), x-bluereplies-delivery-id (idempotency key for the delivery itself).

Verify the signature

Always verify x-bluereplies-signature against the raw request body using your signing secret. Reject anything that doesn't match — it's the only way to know the payload is really from us.

webhook.js
import crypto from 'node:crypto';
export function verifyWebhook(req, signingSecret) {
const sig = req.headers['x-bluereplies-signature'];
if (!sig) return false;
const expected = crypto
.createHmac('sha256', signingSecret)
.update(req.rawBody) // raw bytes BEFORE JSON.parse!
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(sig, 'hex'),
Buffer.from(expected, 'hex'),
);
}

Use raw request body, not the parsed JSON

You must compute the HMAC over the EXACT bytes you received. If you let your framework parse and re-serialize the JSON, the signature won't match (key order, whitespace, etc.). In Express, use express.raw({ type: 'application/json' }) for the webhook route.

Retries

If your endpoint returns anything but a 2xx status code (or times out after 30s), we retry with exponential backoff:

Attempt 1 — immediate
Attempt 2 — 30 seconds later
Attempt 3 — 2 minutes later
Attempt 4 — 10 minutes later
Attempt 5 — 1 hour later
Attempt 6 — 6 hours later
Attempt 7 — 24 hours later (final)

We use the same deliveryId across retries. Make your handler idempotent by checking + storing the deliveryId.

You can replay any delivery on demand from the dashboard's Webhook Deliveries page, or via POST /v1/webhooks/:id/deliveries/:did/replay.

Event catalogue

Subscribe to any combination of these events:

Messages

  • message.inboundAn iMessage was received from a recipient.
  • message.sentOutbound message left our line and is in flight.
  • message.deliveredApple confirmed delivery to the recipient device.
  • message.readRecipient read receipt fired (if they have read receipts on).
  • message.failedSend failed. Payload includes the error reason.
  • message.editedA previously-sent message was edited.
  • message.unsentA previously-sent message was unsent.
  • message.reactionA tapback/reaction was added or removed.
  • message.typingRecipient is typing (or stopped typing).

Messages — fallback

  • message.fallbackFired when an iMessage send fell back to SMS (recipient not on iMessage).

Calls

  • call.initiatedFaceTime call placed.
  • call.ringingRecipient device is ringing.
  • call.connectedRecipient accepted the call.
  • call.completedCall ended normally; includes durationSeconds.
  • call.no_answerCall timed out without being answered.
  • call.failedCall failed to connect.

Lines

  • line.status_changedLine transitioned between active / degraded / inactive / error.
  • line.ban_detectedHeuristic flagged the line as likely banned (consecutive failures).
  • line.ban_recoveredLine returned to healthy state from suspected ban.

Groups

  • group.createdNew group chat created.
  • group.renamedGroup name was updated.
  • group.member_addedA participant was added.
  • group.member_removedA participant was removed (or left).

Contacts

  • contact.opted_outA recipient was added to your opt-out list (auto-detection or manual).

Workflows + campaigns

  • workflow.node_executedA workflow node executed for an enrollment.
  • workflow.completedWorkflow run reached an end node.
  • workflow.failedWorkflow run errored out.
  • campaign.step_sentA step in a campaign sequence sent.
  • campaign.completedCampaign sequence finished for one contact.

Manage webhooks

GET/v1/webhooks

List your registered webhooks.

PATCH/v1/webhooks/:id

Update URL, events, or active flag.

DELETE/v1/webhooks/:id

Remove a webhook permanently.

GET/v1/webhooks/:id/deliveries

Recent delivery attempts with status, response code, latency.