Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ArnasDon/wacrm/llms.txt

Use this file to discover all available pages before exploring further.

Wacrm’s Webhooks API lets you register HTTPS endpoints that receive real-time event notifications when things happen in your account — new messages, delivery status changes, and new conversations — without polling the read endpoints. Each delivery is signed with HMAC-SHA256 so you can verify that it genuinely came from your Wacrm instance.
Migration required. Webhooks require the supabase/migrations/028_webhook_endpoints.sql migration to be applied to your Supabase project before the endpoints and delivery machinery are available.

Events

EventFires when
message.receivedAn inbound message arrives from a contact
message.status_updatedA sent message changes delivery status (e.g. delivered, read)
conversation.createdA new conversation is opened for a contact

Managing endpoints

All webhook management endpoints require the webhooks:manage scope.

POST /api/v1/webhooks — Register an endpoint

Register a new webhook endpoint to start receiving events. Request body:
url
string
required
The HTTPS URL Wacrm will POST events to. Must begin with https://. Localhost, RFC1918 ranges, link-local addresses (including the cloud metadata address 169.254.169.254), and other non-public targets are rejected — see SSRF restrictions below.
events
string[]
required
A non-empty array of event names to subscribe to. Valid values: message.received, message.status_updated, conversation.created.
curl -X POST https://your-crm.example.com/api/v1/webhooks \
  -H "Authorization: Bearer wacrm_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/hooks/wacrm", "events": ["message.received"] }'
Response (201):
{
  "data": {
    "id": "wh1a2b3c-…",
    "url": "https://example.com/hooks/wacrm",
    "events": ["message.received"],
    "is_active": true,
    "failure_count": 0,
    "created_at": "2026-07-01T10:00:00.000Z",
    "secret": "whsec_…"
  }
}
The secret field is returned exactly once — in the 201 response to this request. Wacrm stores only an encrypted copy and cannot show it again. Copy it immediately and store it securely (e.g. in your secrets manager or environment variables). If you lose it, delete the endpoint and create a new one.

GET /api/v1/webhooks — List endpoints

Returns all registered webhook endpoints for your account. The secret is never returned in list or read responses — only at creation.
curl https://your-crm.example.com/api/v1/webhooks \
  -H "Authorization: Bearer wacrm_live_xxx"

GET /api/v1/webhooks/{id} — Read one endpoint

Returns a single endpoint by UUID. Returns 404 if it does not exist or belongs to a different account. The secret is not returned.
curl https://your-crm.example.com/api/v1/webhooks/wh1a2b3c-… \
  -H "Authorization: Bearer wacrm_live_xxx"

PATCH /api/v1/webhooks/{id} — Update an endpoint

Updates one or more fields. Only fields present in the request body are changed.
url
string
New HTTPS destination URL. Must satisfy the same https:// and public-address requirements as registration.
events
string[]
Replaces the current event subscription list. Must be a non-empty array of known event names.
is_active
boolean
Enable (true) or disable (false) the endpoint. Re-enabling an endpoint resets failure_count to zero, giving it a clean slate to start receiving deliveries again.
Returns 404 if the endpoint belongs to a different account.
curl -X PATCH https://your-crm.example.com/api/v1/webhooks/wh1a2b3c-… \
  -H "Authorization: Bearer wacrm_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": true }'

DELETE /api/v1/webhooks/{id} — Remove an endpoint

Permanently removes the endpoint. Future events will not be delivered to the associated URL. Returns 404 if the endpoint belongs to a different account.
curl -X DELETE https://your-crm.example.com/api/v1/webhooks/wh1a2b3c-… \
  -H "Authorization: Bearer wacrm_live_xxx"
Response (200):
{ "data": { "id": "wh1a2b3c-…", "deleted": true } }

Delivery payload

Every event delivery is an HTTP POST to your registered URL with a JSON body using this envelope:
{
  "id": "8f3c1d2e-…",
  "event": "message.received",
  "occurred_at": "2026-07-01T12:00:00.000Z",
  "account_id": "acc-uuid-…",
  "data": { }
}
id
string
A unique UUID for this delivery attempt. Use this to deduplicate events — the same logical event may occasionally be delivered more than once.
event
string
The event name: message.received, message.status_updated, or conversation.created.
occurred_at
string
ISO 8601 timestamp of when the event occurred in Wacrm.
account_id
string
The UUID of your account. Useful when a single endpoint receives webhooks from multiple Wacrm instances.
data
object
Event-specific payload. Shape varies by event — see below.

data shape by event

// message.received
{
  "conversation_id": "conv-uuid-…",
  "contact_id": "contact-uuid-…",
  "whatsapp_message_id": "wamid.…",
  "content_type": "text",
  "text": "Hi 👋"
}

// conversation.created
{
  "conversation_id": "conv-uuid-…",
  "contact_id": "contact-uuid-…"
}

// message.status_updated
{
  "whatsapp_message_id": "wamid.…",
  "conversation_id": "conv-uuid-…",
  "status": "delivered"
}

Delivery headers

Each request also includes these HTTP headers:
HeaderValue
X-Wacrm-EventThe event name (e.g. message.received)
X-Wacrm-Webhook-IdThe UUID of your registered endpoint
X-Wacrm-SignatureHMAC-SHA256 signature — see below

Verifying signatures

Every delivery includes an X-Wacrm-Signature header with this format:
X-Wacrm-Signature: t=<unix_seconds>,v1=<hex>
v1 is computed as:
HMAC-SHA256(secret, "${t}.${rawBody}")
where secret is the plaintext secret shown at endpoint creation and rawBody is the raw (unparsed) request body bytes. Verification steps:
  1. Parse t (timestamp) and v1 (signature hex) from the header.
  2. Recompute the expected HMAC over the raw body using your stored secret.
  3. Compare in constant time to prevent timing attacks.
  4. Reject the delivery if t is more than a few minutes old (replay protection).
import crypto from 'crypto';

function verifyWacrmSignature(header, rawBody, secret) {
  const match = header.match(/t=(\d+),v1=([0-9a-f]+)/);
  if (!match) return false;

  const [, t, v1] = match;

  // Replay protection: reject if the timestamp is more than 5 minutes old.
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest('hex');

  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
Always verify signatures before processing a delivery. Without verification, any party that knows your endpoint URL can inject arbitrary events into your system.

Delivery semantics

Understanding how Wacrm delivers events helps you build a robust consumer:
  • Best-effort, single attempt. Each event triggers one HTTP POST. There is no automatic retry on failure.
  • Short timeout, no redirects. Wacrm uses a short request timeout and does not follow redirects. Your endpoint must respond promptly (return a 2xx before the timeout) and must not issue a redirect.
  • Failure counting and auto-disable. Each consecutive failed delivery increments failure_count on the endpoint. After enough consecutive failures, the endpoint is automatically set to is_active: false. Re-enable it with PATCH /api/v1/webhooks/{id} (which resets failure_count to zero).
  • message.status_updated quirks. This event covers messages Wacrm has a record for (inbox messages and API sends), not broadcast-only sends. Because Meta can re-send and re-order status callbacks, the same status transition may arrive more than once or out of sequence — always dedupe on id and do not assume ordering.
  • Dedupe on id. Every delivery envelope has a unique id. Process it idempotently and ignore duplicates.
Durable retries are a future enhancement. Today, delivery is best-effort with no retry queue. If your use case requires guaranteed at-least-once delivery, reconcile missed events using the read endpoints (GET /api/v1/conversations, GET /api/v1/conversations/{id}/messages) when a gap is detected.

SSRF restrictions

To prevent server-side request forgery, Wacrm validates the destination URL before attempting delivery. The following targets are blocked:
  • URLs that do not begin with https://
  • localhost and loopback addresses (127.0.0.0/8, ::1)
  • RFC1918 private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
  • Link-local addresses (169.254.0.0/16), including the cloud instance metadata endpoint at 169.254.169.254
  • Other non-public or internal network targets
Endpoints that resolve to any of these addresses are rejected at registration time and at delivery time. Use a publicly routable HTTPS URL for your webhook receiver.

Build docs developers (and LLMs) love