CRPRO Hub

Webhooks

Each webhook endpoint gets a whsec_... secret shown exactly once, when it is created. The body of every delivery is signed with no later normalization — the signature covers the exact bytes that were sent.

Signature headers

Every delivery arrives with three headers:

  • X-Hub-Delivery-Id — unique UUID for this attempt, used to deduplicate.
  • X-Hub-Timestamp — the moment it was sent, in seconds since the Unix epoch.
  • X-Hub-Signature-256 — HMAC-SHA256 signature of the raw body, in the format sha256=HEX.

Verifying in Node

Compute the HMAC of the raw body with the endpoint secret and compare it with timingSafeEqual, never with === — a plain string comparison leaks execution time per byte position, which gives an attacker a way to discover the correct signature by trial and error.

verify-signature.js
import { createHmac, timingSafeEqual } from 'node:crypto'
const expected = createHmac('sha256', process.env.WEBHOOK_SECRET).update(rawBody).digest('hex')
const received = request.headers['x-hub-signature-256']?.replace('sha256=', '')
const valid = !!received &&
  /^[0-9a-f]{64}$/.test(received) &&
  timingSafeEqual(Buffer.from(received, 'hex'), Buffer.from(expected, 'hex'))

Receiving rules

  • Read the raw body before parsing JSON — reserializing the JSON can change the expected signature.
  • Reject any delivery without the signature header, or with an invalid signature.
  • Deduplicate by X-Hub-Delivery-Id: a retry resends the same id.
  • Respond 2xx within 10 seconds — process the event asynchronously if it takes longer.

Retries and pausing

A delivery that fails (timeout, a response outside 2xx, connection error) is resent up to ten times. After 20 consecutive failures the endpoint pauses automatically — re-enable it in the dashboard once you have fixed what was blocking delivery.

Blocked destinations

A webhook endpoint URL goes through a destination check. These are blocked:

  • Plain HTTP — only HTTPS is accepted.
  • Internal networks (loopback, link-local and private IP ranges).
  • Redirects — delivery does not follow an HTTP redirect.
  • Private DNS — a domain that resolves to an internal IP is refused.

Payload modes

Each endpoint picks one of two payload modes:

  • normalized (default) — the CRPRO Hub envelope: {"id", "event", "channel_id", "occurred_at", "data"}, identical for every event.
  • meta_raw — Meta’s raw payload for the event; with no raw counterpart, it falls back to the normalized envelope.

Event types

An endpoint subscribes to one or more of the following events:

  • channel.created
  • channel.connected
  • channel.degraded
  • channel.disconnected
  • message.received
  • message.sent
  • message.status
  • message.echo
  • template.status
  • phone.quality
  • account.update
  • webhook.test