MessengerFlow

Webhooks

Receive real-time HTTP notifications when events occur in MessengerFlow.

Webhooks

Webhooks are available on the Momentum plan and above.

Webhooks deliver real-time HTTP POST requests to your server whenever something happens in MessengerFlow — a message is sent, a lead replies, a campaign finishes, or an account status changes.

How It Works

  1. You register an HTTPS endpoint and choose which events to subscribe to
  2. When a subscribed event occurs, MessengerFlow sends a signed HTTP POST to your endpoint
  3. Your server verifies the signature, processes the payload, and returns a 2xx response
  4. If delivery fails, the system retries with exponential backoff up to 5 times

Setting Up

Register an endpoint

  1. Go to Settings > Developers > Webhooks
  2. Click Add Endpoint
  3. Enter your HTTPS URL
  4. Select the events you want to receive
  5. Click Create
  6. Copy the signing secret — it is shown only once and cannot be retrieved later

You can also register endpoints programmatically via the API.

Endpoint requirements

Your endpoint must:

  • Be accessible over HTTPS (HTTP is not accepted)
  • Accept POST requests with a JSON body
  • Return a 2xx status code within 10 seconds
  • Be idempotent — the same event may be delivered more than once in rare cases

Events

Message events

EventTrigger
message.sentA campaign message was successfully delivered to a lead
message.failedA campaign message failed to deliver

Conversation events

EventTrigger
conversation.createdA new conversation was detected during inbox sync
conversation.replyAn inbound reply was detected during inbox sync
conversation.bookedA conversation was marked as booked in the app or via API

Account events

EventTrigger
account.status_changedAn account status flag changed (suspended, rate limited, invalid credentials, etc.)

Campaign events

EventTrigger
campaign.completedAll leads in a campaign have been processed

Payload Format

Every webhook delivery is a JSON POST with this structure:

{
  "event": "event.type",
  "timestamp": "2026-02-10T14:30:00.000Z",
  "data": { }
}
FieldTypeDescription
eventstringThe event type that triggered this delivery
timestampstringISO 8601 timestamp of when the event occurred
dataobjectEvent-specific payload (see below)

message.sent

{
  "event": "message.sent",
  "timestamp": "2026-02-10T14:30:00.000Z",
  "data": {
    "campaign_id": "a1b2c3d4-...",
    "lead_id": "e5f6a7b8-...",
    "account_id": "c9d0e1f2-...",
    "message": "Hey John, I noticed your page and wanted to reach out..."
  }
}

message.failed

{
  "event": "message.failed",
  "timestamp": "2026-02-10T14:30:00.000Z",
  "data": {
    "campaign_id": "a1b2c3d4-...",
    "lead_id": "e5f6a7b8-...",
    "account_id": "c9d0e1f2-...",
    "error": "Account rate limited by Facebook"
  }
}

conversation.created

{
  "event": "conversation.created",
  "timestamp": "2026-02-10T14:30:00.000Z",
  "data": {
    "conversation_id": 12345,
    "account_id": "c9d0e1f2-...",
    "recipient_id": "100001234567890"
  }
}

conversation.reply

{
  "event": "conversation.reply",
  "timestamp": "2026-02-10T14:30:00.000Z",
  "data": {
    "conversation_id": 12345,
    "account_id": "c9d0e1f2-...",
    "recipient_id": "100001234567890",
    "message": "Hi! Yes, I'd love to learn more."
  }
}

conversation.booked

{
  "event": "conversation.booked",
  "timestamp": "2026-02-10T14:30:00.000Z",
  "data": {
    "conversation_id": "uuid",
    "booked_at": "2026-02-15T10:00:00.000Z"
  }
}

account.status_changed

Fired when any account status flag is modified, either by the automation worker or manually through the app.

{
  "event": "account.status_changed",
  "timestamp": "2026-02-10T14:30:00.000Z",
  "data": {
    "account_id": "c9d0e1f2-...",
    "email": "[email protected]",
    "field": "banned",
    "value": true
  }
}

The field indicates which status changed. Possible values:

FieldMeaning
bannedAccount suspended by Facebook
rate_limited_untilRate limit applied or cleared
invalid_credentialsPassword no longer valid
invalid_2fa2FA secret incorrect
errorGeneric error flag
unsupported_auth_flowFacebook requires unsupported verification
requires_pinAccount needs PIN verification
enabledAccount enabled or disabled by user

campaign.completed

{
  "event": "campaign.completed",
  "timestamp": "2026-02-10T14:30:00.000Z",
  "data": {
    "campaign_id": "a1b2c3d4-...",
    "name": "Q1 Outreach"
  }
}

Signature Verification

Every delivery includes headers that let you verify the request is genuinely from MessengerFlow:

HeaderDescription
X-MessengerFlow-EventEvent type (e.g. message.sent)
X-MessengerFlow-SignatureHMAC signature: sha256=<hex>
X-MessengerFlow-DeliveryUnique delivery ID (use for idempotency)
X-MessengerFlow-TimestampUnix timestamp of the request

The signature is computed as:

HMAC-SHA256(signing_secret, timestamp + "." + raw_json_body)

Node.js example

const crypto = require('crypto');

function verifySignature(secret, timestamp, rawBody, signature) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');
  return signature === `sha256=${expected}`;
}

app.post('/webhooks/messengerflow', (req, res) => {
  const signature = req.headers['x-messengerflow-signature'];
  const timestamp = req.headers['x-messengerflow-timestamp'];
  const rawBody = JSON.stringify(req.body);

  if (!verifySignature(process.env.WEBHOOK_SECRET, timestamp, rawBody, signature)) {
    return res.status(401).send('Invalid signature');
  }

  const { event, data } = req.body;

  switch (event) {
    case 'message.sent':
      console.log(`Message delivered to lead ${data.lead_id}`);
      break;
    case 'conversation.reply':
      console.log(`New reply from ${data.recipient_id}: ${data.message}`);
      break;
    case 'conversation.booked':
      // Push to your CRM
      break;
  }

  res.status(200).send('OK');
});

Python example

import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = "your_signing_secret"

def verify_signature(secret, timestamp, body, signature):
    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.{body}".encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, f"sha256={expected}")

@app.route("/webhooks/messengerflow", methods=["POST"])
def handle_webhook():
    signature = request.headers.get("X-MessengerFlow-Signature", "")
    timestamp = request.headers.get("X-MessengerFlow-Timestamp", "")
    raw_body = request.get_data(as_text=True)

    if not verify_signature(WEBHOOK_SECRET, timestamp, raw_body, signature):
        abort(401)

    payload = request.get_json()
    event = payload["event"]
    data = payload["data"]

    if event == "conversation.reply":
        print(f"New reply from {data['recipient_id']}")
    elif event == "conversation.booked":
        # Push to your CRM
        pass

    return "OK", 200

Retry Logic

When a delivery fails (non-2xx response or timeout), the system retries with increasing delays:

AttemptRetry after
11 minute
25 minutes
330 minutes
42 hours
524 hours

After 5 failed attempts, the delivery is marked as failed and the endpoint's failure counter increments.

Auto-disable: If an endpoint accumulates 5 consecutive delivery failures, it is automatically disabled to prevent further wasted requests. You can re-enable it from Settings > Developers > Webhooks after fixing the issue.

Testing

Every endpoint has a Send Test button that delivers a ping event with a valid HMAC signature. Use it to confirm:

  • Your endpoint is publicly reachable
  • HTTPS is working correctly
  • Signature verification passes
  • Your server returns a 2xx response

You can also send a test via the API.

Best Practices

  1. Return 200 immediately — acknowledge receipt before doing any heavy processing. Queue the payload for async handling in your application.
  2. Verify every signature — always validate the HMAC to confirm the request is from MessengerFlow, not a malicious third party.
  3. Use the delivery ID for idempotency — in rare cases, the same event may be delivered more than once. Use the X-MessengerFlow-Delivery header to deduplicate.
  4. Monitor your endpoint health — check the delivery history in Settings > Developers > Webhooks to catch failures early. Repeated failures will auto-disable your endpoint.
  5. Keep your endpoint fast — responses must arrive within 10 seconds. If processing takes longer, store the payload and handle it asynchronously.
  6. Log raw payloads — during development, log the full request body and headers for debugging. This helps diagnose signature verification issues.

Troubleshooting

Endpoint not receiving events

  • Verify the URL is publicly accessible (not localhost or behind a firewall)
  • Confirm HTTPS is configured correctly with a valid certificate
  • Check that the endpoint is enabled and not auto-disabled
  • Ensure you're subscribed to the correct events

Signature verification failing

  • Make sure you're using the raw JSON body string, not a re-serialized version
  • Verify the signing secret matches what was shown at endpoint creation
  • Check that the timestamp header is included in the HMAC computation
  • Ensure you're comparing the full sha256=<hex> prefix

Endpoint auto-disabled

Your endpoint returned non-2xx responses 5 consecutive times. To recover:

  1. Check your server logs for the root cause
  2. Fix the issue (server error, timeout, certificate problem, etc.)
  3. Go to Settings > Developers > Webhooks
  4. Click into the endpoint and re-enable it
  5. Use Send Test to confirm it's working before live events resume

Events arriving late

  • Worker-originated events (messages, conversations) are delivered on a 10-second polling cycle
  • If previous deliveries are in retry backoff, new events queue behind them
  • Check the delivery history to see if earlier failures are causing delays