Skip to content
MotherBot
Integrations

Webhooks

Receive real-time HTTP notifications when things happen in MotherBot — messages and delivery status, conversations, deals, sequences, coupons, carts, links, number health, template approvals and calls.

How Webhooks Work

When an event occurs (a message is received, a campaign completes, etc.), MotherBot sends an HTTP POST request to your configured endpoint with a JSON payload. Your server should respond with a 200 OK within 10 seconds.

Note

Each request times out after 10 seconds. A non-2xx response or a timeout is retried up to 5 attempts with exponential backoff starting at 1 second; after that the delivery is dropped and the failure — with the reason your server gave — is recorded against the endpoint.

Warning

Answer quickly and do the work afterwards. Ten seconds is the whole budget, and an endpoint that processes the event before replying will start timing out under load — which turns one slow handler into five duplicate deliveries.

Setting Up a Webhook

1

Create an endpoint on your server

Your endpoint must be publicly accessible over HTTPS. For local development, use a tunnel like ngrok:
ngrok http 3000
# Copy the https:// forwarding URL
2

Register the endpoint in MotherBot

Go to Dashboard → Webhooks → Add Endpoint. Enter your URL, name it, and tick the events you want. You can do the same from the REST API withPOST /api/v1/webhooks, which also returns the full event catalogue onGET /api/v1/webhooks.

Warning

The URL must be publicly reachable. Private, loopback and cloud-metadata addresses are rejected when you register, and re-checked before every delivery.
3

Copy the signing secret

MotherBot generates a Signing Secret for each webhook. Store it securely — you'll use it to verify that requests are from MotherBot.
4

Verify the signature

Every webhook request includes an X-MotherBot-Signature header:
const crypto = require("crypto");

function verifyWebhook(payload, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)        // raw request body (string)
    .digest("hex");
  return `sha256=${expected}` === signature;
}

// In your Express handler:
app.post("/webhook", (req, res) => {
  const sig = req.headers["x-motherbot-signature"];
  if (!verifyWebhook(req.rawBody, sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send("Invalid signature");
  }
  const event = req.body;
  // handle event...
  res.sendStatus(200);
});

Event Types

Every event below is dispatched from real code — there are no placeholders. The same catalogue, with machine-readable keys and descriptions, comes back fromGET /api/v1/webhooks, so you can build your own picker instead of hard-coding a list that will grow.

Messages

EventFires when
message.receivedA customer messaged you — on WhatsApp, Instagram, Messenger or the website widget.
message.sentAn outbound message was accepted by WhatsApp.
message.deliveredA sent message reached the recipient's phone.
message.readThe recipient opened a delivered message.
message.failedA message could not be delivered. The payload carries Meta's own error code and reason.

Contacts

EventFires when
contact.createdA new contact was added — by an inbound message, an import, or the API.
contact.updatedA contact's details, tags or custom fields changed.
contact.deletedA contact was deleted. Fires once per contact, including inside a bulk delete — so your own copy can be removed too.
contact.enrichedExplorium returned data for a contact — job title, employer, verified work email, company firmographics. Fires only on a successful match, so it is a clean trigger for lead routing or scoring.
contact.opted_outA contact asked to stop hearing from you (a STOP-style keyword, or opt-out set by hand). Marketing to them after this is a compliance problem, so this is the one contact event worth alerting on.

Conversations & Desk

EventFires when
conversation.openedA new support conversation began — the first inbound message after the last one was resolved.
conversation.closedAn agent resolved a conversation. Carries how long it was open, for your own reporting.
csat.submittedA customer answered the satisfaction question after a conversation was resolved.
agent.assignedA conversation was assigned to a team member, by hand or by auto-assignment.

Broadcasts

EventFires when
campaign.startedA broadcast began sending.
campaign.completedA broadcast finished. Carries the final sent/delivered/failed counts.
campaign.pausedA running broadcast was paused — by a person, or by Number Health Guardian stepping in to protect the number.

Sequences

EventFires when
sequence.enrolledA contact entered a drip sequence.
sequence.completedA contact received the last step of a sequence.
sequence.exitedA contact left a sequence before the end — usually because they replied, which is the outcome the sequence was for.

Chatbots & Flows

EventFires when
chatbot.triggeredA chatbot session started for a contact.
chatbot.completedA chatbot session ended.
flow.submittedA customer submitted a WhatsApp Flow form. Carries the answers.
knowledge_base.document_processedA document finished indexing into an AI Knowledge Base — ready or errored, with the reason when it failed.

Deals & Pipeline

EventFires when
deal.createdA deal was opened against a contact.
deal.stage_changedA deal moved between pipeline stages. Carries both the old and the new stage.
deal.wonA deal was marked won — the event to post into your accounting or fulfilment system.
deal.lostA deal was marked lost, with the reason when one was given.

Store & Coupons

EventFires when
coupon.redeemedA discount code was redeemed — from your checkout, or recorded through the API.
cart.abandonedA shopper left items in their cart and became eligible for a recovery message.
catalog_order.createdA customer submitted WhatsApp's native cart/catalog checkout. Fires on capture, independent of whether it's ever pushed into your store.

Links & Ads

EventFires when
link.clickedSomeone opened one of your trackable links. Carries the ref code, so a click on a specific poster is distinguishable from one on an email.
ad.referralA conversation started from a Click-to-WhatsApp ad, with the ad and headline it came from.

Number Health

EventFires when
number.quality_changedMeta changed a number's quality rating. A drop to YELLOW is the earliest warning you get before restrictions.
number.sending_pausedGuardian paused sending on a number to protect it. Anything you queue while this is in force will not go out.

Templates

EventFires when
template.approvedMeta approved a message template — it can now be used in broadcasts.
template.rejectedMeta rejected a template, with the reason they gave.

Payments

EventFires when
payment.receivedA WhatsApp payment succeeded.
payment.failedA WhatsApp payment failed or was cancelled.

Calls

EventFires when
call.completedA WhatsApp call ended, with its duration and direction.
call.missedAn inbound call was not answered — the one worth calling somebody back about.

Scheduling

EventFires when
calendly.booking_createdA contact booked (or rescheduled into) a Calendly event type. Does not re-fire for the routing-form step, only a real booking.
calendly.booking_canceledA contact canceled a Calendly booking. Does not fire for the cancel half of a reschedule — that's a booking_created for the new time, not a real cancellation.

Lead sources

EventFires when
indiamart.lead_receivedA buyer enquiry arrived from IndiaMART Lead Manager — by real-time push or by the pull sweep, whichever reached it first. Fires once per UNIQUE_QUERY_ID, never for a duplicate, and never for leads collected by a historical import.

Quotations

EventFires when
quotation.createdA quotation was drafted — by a person, by a chatbot, or through the API. Fires while it is still a draft, so it is the hook for pushing a draft into your own system before anyone sees it.
quotation.sentA quotation was put in front of the customer, by email, WhatsApp or a shared link. Carries which channel it went out on.
quotation.viewedThe customer opened the quotation link. Fires on every open, not just the first, with the running view count — repeated opens without a decision is the signal to call them.
quotation.acceptedThe customer accepted, with the name they signed under and the exact time. This is the event to raise an invoice or a work order from.
quotation.rejectedThe customer declined, with the reason when they gave one.
quotation.expiredA quotation passed its valid-until date without a decision. Fires once, from the sweep.
quotation.cancelledSomeone on your team withdrew a quotation that had already gone out.
quotation.revisedA new version replaced an earlier one. Carries both the old and the new quotation ids, so your copy can be superseded rather than duplicated.
quotation.remindedAn automatic follow-up went to a customer who had not responded yet.
quotation.approval_requestedA quotation crossed the value or discount threshold and is waiting for a manager to sign off before it can be sent.
quotation.approvedA manager approved a quotation that was waiting for sign-off — it can now be sent.
quotation.approval_rejectedA manager sent a quotation back instead of approving it, with their comment.

Payload Format

Every delivery is a POST with this envelope. The event-specific fields are insidepayload, andevent is repeated there so a handler can switch on the object alone.
{
  "event": "message.received",
  "payload": { /* event-specific, see below */ },
  "timestamp": "2026-07-27T09:32:11.000Z"
}
Two headers travel with it:
HeaderValue
X-MotherBot-EventThe event key, so you can route without parsing the body
X-MotherBot-Signaturesha256=<hex HMAC of the raw body, keyed with your signing secret> — only when a secret is set

Warning

Verify the signature against the raw request body, before any JSON parsing or re-serialisation. A body that has been parsed and stringified again is a different string, and the HMAC will never match.
A message.received delivery:
{
  "event": "message.received",
  "payload": {
    "event": "message.received",
    "messageId": "665f2a...",
    "wamid": "wamid.HBgMOTE5...",
    "from": "919876543210",
    "contactId": "664a1f...",
    "whatsappAccountId": "6640a...",
    "type": "text",
    "preview": "Hello, I need help with my order",
    "timestamp": "2026-07-27T09:32:10.000Z"
  },
  "timestamp": "2026-07-27T09:32:11.000Z"
}
A deal.won delivery — every deal event carries the same deal object, so one handler covers all four:
{
  "event": "deal.won",
  "payload": {
    "event": "deal.won",
    "dealId": "670c11...",
    "title": "Bulk order — 200 units",
    "value": 48000,
    "currency": "INR",
    "pipelineId": "66f0aa...",
    "stage": "won",
    "status": "won",
    "previousStage": "negotiation",
    "contactId": "664a1f...",
    "contactName": "Asha Patel",
    "contactPhoneNumber": "+919876543210",
    "ownerId": "6641b...",
    "ownerName": "Ravi",
    "expectedCloseAt": null,
    "timestamp": "2026-07-27T09:40:02.000Z"
  },
  "timestamp": "2026-07-27T09:40:02.000Z"
}
A number.sending_paused delivery — the one worth alerting a human about, because anything queued while it holds will not go out:
{
  "event": "number.sending_paused",
  "payload": {
    "event": "number.sending_paused",
    "accountId": "66aa12...",
    "pausedUntil": "2026-07-28T09:40:02.000Z",
    "reason": "Guardian paused sending: Meta withheld 4.2% of "Diwali offer" ...",
    "trigger": "circuit_breaker",
    "blockRate": 0.042,
    "campaignId": "665c3b...",
    "timestamp": "2026-07-27T09:40:02.000Z"
  },
  "timestamp": "2026-07-27T09:40:02.000Z"
}

Note

Payload fields are added over time, never renamed or removed. Ignore keys you do not recognise rather than validating strictly against this list.

Testing & Troubleshooting

  • Use a tunnel (ngrok, Cloudflare Tunnel) to point an endpoint at your machine while you build the handler.
  • Dashboard → Webhooks shows each endpoint's delivered and failed counts, when it last succeeded, and the exact error your server returned on the last failure.
  • Pause an endpoint with the Pause button (or isActive: false on the API) while you deploy — deliveries stop, the endpoint keeps its id, stats and its slot against your plan's webhook quota.
  • Nothing arriving? Check the endpoint is active, that the event is actually ticked, and that the URL is publicly reachable — a private address is refused at delivery time.

Note

An endpoint can be scoped to one WhatsApp number. When it is, it only receives events for that number — which is how a business running two brands on two numbers keeps their integrations separate.