Dashboard session

DLR Webhooks

Configure a delivery receipt webhook and verify signed message status events.

GET
/v1/webhooks/dlr
User session - Read your configured DLR webhook.
PUT
/v1/webhooks/dlr
User session - Create or update your DLR webhook endpoint.
POST
/v1/webhooks/dlr/rotate-secret
User session - Rotate the webhook signing secret.
DELETE
/v1/webhooks/dlr
User session - Delete your DLR webhook.

Overview

DLR webhooks let your application receive message status changes without polling GET /v1/messages/:message_id.

Bar9 sends a webhook when an SMS message moves through delivery states such as sent, delivered, or failed. Each request is signed with your webhook signing secret so your server can verify that the event came from Bar9 and was not modified in transit.

Configure a webhook

Open the dashboard, go to Webhooks, and enter your HTTPS endpoint URL. The URL must use https://; local and private network hosts are rejected.

When you create a webhook, Bar9 shows the signing secret once. Store it securely. Existing secrets are only shown as a preview. If you lose the secret, rotate it from the same dashboard page.

You can also configure the endpoint through the authenticated dashboard API:

curl https://api.bar9.me/v1/webhooks/dlr \
  -H "Content-Type: application/json" \
  --cookie "$BAR9_SESSION_COOKIE" \
  -X PUT \
  -d '{
    "url": "https://example.com/webhooks/bar9/dlr",
    "enabled": true
  }'

Event request

Bar9 sends a POST request with JSON body and these headers:

HeaderNotes
X-Bar9-Event-IDUnique event id. Store it to make processing idempotent.
X-Bar9-Event-TypeEvent name, for example message.delivered.
X-Bar9-TimestampUnix timestamp used in the signature.
X-Bar9-SignatureHMAC-SHA256 signature with a v1= prefix.
Content-Typeapplication/json.

Example payload:

{
	"id": "evt_...",
	"type": "message.delivered",
	"created_at": 1778407200,
	"data": {
		"id": "msg_...",
		"to": "+213661000000",
		"sender": "BAR9",
		"type": "message",
		"status": "delivered",
		"provider_message_id": "abc123",
		"segments": 1,
		"cost_credits": 5,
		"client_reference": "order-1001",
		"created_at": 1778407000,
		"queued_at": 1778407000,
		"sent_at": 1778407100,
		"delivered_at": 1778407200,
		"failed_at": null,
		"failure_reason": null
	}
}

Verify signatures

Build the signed payload as:

{X-Bar9-Timestamp}.{X-Bar9-Event-ID}.{raw request body}

Compute HMAC-SHA256 with your webhook signing secret and compare it to X-Bar9-Signature after removing the v1= prefix. Use a constant-time comparison and reject timestamps outside a short window such as five minutes.

Node.js example:

import crypto from 'node:crypto';

function verifyBar9Webhook({ secret, timestamp, eventId, rawBody, signature }) {
	const value = signature.startsWith('v1=') ? signature.slice(3) : signature;
	const signedPayload = `${timestamp}.${eventId}.${rawBody}`;
	const expected = crypto.createHmac('sha256', secret).update(signedPayload).digest('hex');

	const expectedBytes = Buffer.from(expected, 'hex');
	const valueBytes = Buffer.from(value, 'hex');
	if (expectedBytes.length !== valueBytes.length) return false;

	return crypto.timingSafeEqual(expectedBytes, valueBytes);
}

Handle retries and duplicates

Your endpoint should return a 2xx status only after it has accepted the event. Store X-Bar9-Event-ID and ignore duplicates so repeated deliveries do not trigger the same business action twice.

Process webhooks asynchronously when possible. Keep the endpoint fast, verify the signature before parsing business fields, and do not trust the event until verification succeeds.

Status values

StatusMeaning
sentThe SMS provider accepted or submitted the SMS.
deliveredThe provider reported successful delivery.
failedThe provider reported failure or non-delivery.

Use client_reference to connect a webhook event back to your own order, user, or workflow id.