Skip to content

Webhook Handling

A single webhook endpoint handles all Meta events. Facebook and Instagram share the same URL — the handler dispatches based on the object field in the payload.

GET /integrations/meta-messaging/webhook

Facebook sends this during webhook subscription to verify ownership.

Query params:

ParamValue
hub.mode"subscribe"
hub.verify_tokenMust match meta_messaging_verify_token from core_conf
hub.challengeEcho back on success

Returns the hub.challenge as plain text, or 403 if the token doesn’t match.

POST /integrations/meta-messaging/webhook

Signature verification: Every request includes an X-Hub-Signature-256 header (sha256=<hex>). The handler computes HMAC-SHA256 of the raw body using meta_messaging_app_secret and performs a constant-time comparison. Returns 403 on mismatch.

Response: Always 200 OK — Meta requires fast acknowledgment. All processing happens asynchronously.

The payload’s object field determines routing:

match payload.object.as_str() {
"page" => handle_messenger_events(), // Facebook Messenger
"instagram" => handle_instagram_events(), // Instagram DM
_ => log and ignore,
}

Each entry contains a list of messaging events. The handler processes three types:

Inbound text or media from a user. The handler:

  1. Resolves the organization by Page ID or IG account ID
  2. Resolves or creates a contact (resolve_contact_by_facebook / resolve_contact_by_instagram)
  3. Creates a message record with direction = Inbound, status = Received
  4. Spawns background tasks for contact enrichment and attachment downloads
  5. Publishes the message via WebSocket (EVT_MESSAGE_NEW)

Meta sends a delivery.watermark timestamp (milliseconds since epoch). All outbound messages sent before this timestamp are marked Delivered.

Same as delivery, but marks messages as Read.

When an inbound message arrives from an unknown sender:

  1. Query contact_facebook (or contact_instagram) by PSID/IGSID and org
  2. If not found: create a stub contact and platform record in a transaction
  3. Spawn background enrichment — fetch profile via Graph API:
    • Messenger: GET /{psid}?fields=first_name,last_name,profile_pic
    • Instagram: GET /{igsid}?fields=name,username

Meta doesn’t send per-message delivery confirmations. Instead, it sends a watermark — a timestamp indicating all messages before that point were delivered/read.

update_message_status_by_watermark in update_message_status_by_watermark_service.rs:

  1. Convert watermark (ms) to DateTime<Utc>
  2. Query messages where from_address = <page_or_ig_id> AND created_at < watermark AND status != Read
  3. Batch-update status and publish changes via WebSocket