Webhooks

Receive real-time notifications for message events.

Setting up webhooks

Configure webhooks in the Dashboard → Settings → Webhooks. Each webhook configuration includes:

  • URL - The endpoint that will receive POST requests
  • Events - Which events to subscribe to
  • Secret - Used to sign payloads for verification (auto-generated, AES-encrypted at rest)

Event types

Event Direction Description
sms_received Incoming An SMS was received by a device
sms_sent Outgoing A device confirmed the SMS was sent
sms_delivered Outgoing The carrier confirmed the SMS was delivered
sms_failed Outgoing The SMS could not be sent or delivered

Webhook payloads

Vendel sends a POST request with a JSON body. Keys are sorted alphabetically for deterministic HMAC signing. The payload fields depend on the event type.

sms_received

{
  "body": "Hello, this is a test message",
  "event": "sms_received",
  "from": "+1234567890",
  "message_id": "a1b2c3d4e5f6g7h",
  "timestamp": "2024-01-15 10:30:05.000Z"
}

sms_sent / sms_delivered

{
  "body": "Your verification code is 847293",
  "delivered_at": "2024-01-15 10:31:12.000Z",
  "event": "sms_delivered",
  "message_id": "a1b2c3d4e5f6g7h",
  "sent_at": "2024-01-15 10:30:08.000Z",
  "status": "delivered",
  "timestamp": "2024-01-15 10:30:05.000Z",
  "to": "+1234567890"
}

sms_failed

{
  "body": "Your verification code is 847293",
  "error_message": "FCM not initialized",
  "event": "sms_failed",
  "message_id": "a1b2c3d4e5f6g7h",
  "status": "failed",
  "timestamp": "2024-01-15 10:30:05.000Z",
  "to": "+1234567890"
}

Verifying webhooks

Webhooks include an HMAC-SHA256 signature in the X-Webhook-Signature header. The signature is computed over the entire JSON body using your webhook secret:

X-Webhook-Signature: a1b2c3d4e5f6...

Verify it using your webhook secret:

import hmac
import hashlib

def verify_signature(payload_bytes, signature, secret):
    """Verify the webhook signature.

    Important: Use the raw request body (not re-serialized JSON)
    since the signature is computed over sorted-key JSON.
    """
    expected = hmac.new(
        secret.encode(),
        payload_bytes,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
import { createHmac } from "crypto";

function verifySignature(payloadBuffer, signature, secret) {
  const expected = createHmac("sha256", secret)
    .update(payloadBuffer)
    .digest("hex");
  return expected === signature;
}

Retry policy

Failed webhook deliveries are retried with exponential backoff:

  • 3 retries with increasing delays
  • Webhook marked as failed after all retries are exhausted

Related