Overview

Register a webhook URL and every new inbound message is POSTed to it as a JSON event. Delivery is durable and retried — if your receiver is down, the event is redelivered with exponential backoff. Manage webhooks with the Create, List, and Delete endpoints.

Event Payload

Currently one event type is delivered: message.received.
{
  "event": "message.received",
  "thread_id": "0b6f5c1e-8f4a-4f4b-9a2d-3a1c2b3d4e5f",
  "number": "+12315551234",
  "message": {
    "thread_id": "0b6f5c1e-8f4a-4f4b-9a2d-3a1c2b3d4e5f",
    "guid": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
    "rowid": 4521,
    "text": "Hey, got your message!",
    "from_me": false,
    "service": "iMessage",
    "address": "+15551234567",
    "unix_ms": 1765432100000,
    "is_delivered": true,
    "is_read": false,
    "is_sent": false
  }
}
See Message for field descriptions. number is the sending number of yours that received the message — use it to route events when your account holds more than one number.

Signature Verification

Every delivery is signed with the webhook’s secret (returned once at creation). The signature is sent in the X-BlueBubble-Signature header:
X-BlueBubble-Signature: sha256=<hex HMAC-SHA256 of the raw request body>
Verify it by computing the HMAC of the raw request body with your secret and comparing:
import hashlib
import hmac

def verify(secret: str, body: bytes, signature_header: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode("utf-8"), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)
import crypto from 'crypto';

function verify(secret, rawBody, signatureHeader) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}
Always verify the signature against the raw body bytes, before any JSON parsing or re-serialization — re-encoded JSON will not match the signed bytes.

Delivery Semantics

  • At-least-once delivery. The same event may be delivered more than once; deduplicate on the message guid.
  • Retries. A delivery counts as failed if your endpoint is unreachable, times out, or returns a 4xx/5xx status. Failed deliveries are retried with exponential backoff, then dead-lettered after repeated failures.
  • Respond fast. Return a 2xx quickly (within 10 seconds) and do any heavy processing asynchronously.