Meta webhook handling: X-Hub-Signature-256 payload verification (HMAC-SHA256 over the raw body with your App Secret), the GET subscription challenge, and helpers that pull messages and delivery statuses out of a notification payload.
Functions
change-values
fn (payload: Any): Vec
The value objects of every messages-field change in a notification payload (entry[].changes[].value). Exposed for testing.
extract-messages
fn (payload: Any): Vec
The inbound messages in a webhook notification payload, flattened across entries and changes. Each message is Meta's message object (from, id, timestamp, type, and the type's object such as text.body, image.id, interactive.button_reply, location) plus phone_number_id (the business number that received it) and contact (the sender's {profile: {name}, wa_id} entry, when present). Returns [] for payloads without messages, such as status updates.
Example
messages ::whatsapp::webhooks/extract-messages(request.body)
map(messages, (m) {
if(eq(m.type, "text"), println(`${m.from}: ${m.text.body}`), null)
})
extract-statuses
fn (payload: Any): Vec
The outbound message status updates in a webhook notification payload (sent, delivered, read, failed), flattened across entries and changes, each with phone_number_id added. Each status carries id (the wamid you sent), status, timestamp, recipient_id, and errors on failure.
verify-challenge
fn (query: Map): Str
fn (query: Map, verify-token: Str): Str
Verify a webhook subscription challenge. When you configure a webhook URL in Meta's App Dashboard, Meta sends a GET request with hub.mode, hub.verify_token, and hub.challenge query parameters. Returns the hub.challenge value when the mode is subscribe and the token matches (constant-time comparison), otherwise an Err.
Uses the whatsapp.verify.token context variable by default (failing when it is unset), or accepts an explicit token; an empty explicit token never matches.
Example
::webhooks ::whatsapp::webhooks
// In a webhook handler for GET requests:
result ::webhooks/verify-challenge(request.query)
// Returns the challenge string on success, or an Err on failure:
// reply 200 with the challenge as the plain-text body, else 403.
whatsapp.verify.token
verify-request
fn (request: Map): Bool
fn (request: Map, app-secret: Str): Bool
Verify a WhatsApp webhook request using its HMAC-SHA256 signature. Meta signs the raw payload with your App Secret and sends X-Hub-Signature-256: sha256={hex}. The digest is computed over ::hot::http/raw-body(request) and compared in constant time.
Uses the whatsapp.app.secret context variable by default (failing when it is unset), or accepts an explicit secret; an empty explicit secret never verifies. Meta does not sign a timestamp, so there is no replay window: deduplicate on message id.
Example
::webhooks ::whatsapp::webhooks
// Using context variable (1-arity)
is-valid ::webhooks/verify-request(request)
// With explicit secret (2-arity)
is-valid ::webhooks/verify-request(request, "my-app-secret")
whatsapp.app.secret