Guide to Missive Webhooks: Features and Best Practices
Missive webhooks send conversation data to your systems when a Missive rule fires. Missive is a shared inbox for email, SMS, WhatsApp and chat, and webhooks are how an incoming message, a label change or a reassigned conversation can create a CRM record, open a ticket or start a workflow in another tool.
This guide covers how Missive webhooks are set up as rule actions, what the payload contains, how the optional signature works, and how retries and auto-disabling affect a production receiver.
What are Missive webhooks?
In Missive, a webhook is an action on a rule. You create a rule, give it a type and conditions, and choose Webhook as the action. Whenever the rule matches, Missive sends an HTTP POST with a JSON payload to the webhook URL. So the "events" are whatever your rules match: incoming messages from a certain sender, outgoing replies, a label being applied, and so on.
The payload describes the rule that fired, the conversation, and its latest message, so you can usually act on it without calling the Missive API.
Signing is optional. If you set a validation secret on the rule's webhook action, every request carries an X-Hook-Signature header with sha256= and the hex HMAC-SHA256 of the request body. Without a secret, requests are unsigned.
Missive webhook features
| Feature | Details |
|---|---|
| Configuration | Settings > Rules, with Webhook as the rule's action |
| Who can create | Admins or owners of an organization on the Productive or Business plan |
| Triggers | The rule's type and conditions |
| Validation request | Sent when the rule is saved, to check the endpoint works |
| Signature header | X-Hook-Signature, only when a validation secret is set |
| Scheme | HMAC-SHA256, hex, prefixed with sha256= |
| Signed content | The full request body |
| Secret | The validation secret ("Signature secret") you set on the rule action |
| Response window | 15 seconds |
| Retries | Errors and timeouts, up to 5 times over 8 minutes |
| Auto-disable | After more than 50 failures in a row, until re-enabled in the Rules settings |
| Debugging | The Show rule webhook events preference shows each fired webhook in the conversation timeline |
Missive publishes no delivery ID and no source IP list.
Rule types and the payload
Missive rules come in a few types: incoming messages, outgoing messages, and user actions such as a label change, a new comment, a conversation being closed or reopened, or a change of assignee or team. The payload's rule.type carries the type of the rule that fired. Missive's docs show two values, label_change and incoming_email, but don't list the rest, so log any type you don't recognise.
Here is Missive's example payload, trimmed:
{
"rule": {
"id": "45408b30-aa3a-45n1-bh67-0a0cb8da9080",
"description": "Notify elfs",
"type": "label_change"
},
"conversation": {
"id": "47a57b76-df42-4d8k-927x-80dbe5d87191",
"subject": "Mordor GPS coordinates",
"team": { "id": "2f618f9e-d3d4-4a01-b7d5-57124ab366b8", "name": "Hobbits" },
"assignee_names": "Frodo Baggins",
"shared_label_names": "Elfs",
"messages_count": 1,
"web_url": "https://mail.missiveapp.com/#inbox/conversations/47a57b76-df42-4d8k-927x-80dbe5d87191"
},
"latest_message": {
"id": "86ef8bb8-269c-4959-a4f0-213db4e67844",
"subject": "Fwd: Mordor GPS coordinates",
"preview": "Hi Mr. Gamgee, I discovered something really disturbing about the Mordor coordinates we had.",
"type": "email",
"delivered_at": 1548415828,
"from_field": { "name": "Samwise Gamgee", "address": "sam@fellowship.org" }
}
}
rule.ididentifies the exact rule, which is often a better routing key thanrule.typewhen several rules point at the same URL.conversationcarries the team, assignees, shared labels, authors, counts, and links to open it in the app or on the web.latest_message.typeis the channel, such asemail. It is not the event type.- When the webhook runs as a rule step,
rulealso hasparent_id,delay_secondsanddelay_business_hours.
For WhatsApp, the id values in from_field and to_fields are Business-Scoped User IDs (such as whatsapp:US.13491208655302741918), not phone numbers. Missive flags this as a breaking change for integrations that relied on phone numbers.
Setting up Missive webhooks
- Generate a long random validation secret, for example with
openssl rand -hex 32, and store it server-side asMISSIVE_WEBHOOK_SECRET. - Make sure your endpoint is live and accepts
POSTrequests. Missive sends a validation request when you save the rule. - In Missive, go to Settings > Rules, create a rule, and set its type and conditions.
- Choose Webhook as the action, enter your URL, and set the validation secret.
- Save the rule.
If you use Hookdeck, create a source with the Missive source type, paste the same validation secret into it, and use the Hookdeck source URL as the webhook URL. Set a validation secret on the rule's webhook action. Without one, deliveries are unsigned and fail verification in Hookdeck. Hookdeck accepts Missive's validation request when you save the rule, so the save succeeds. Missive doesn't document whether that request is signed, so it may appear in Hookdeck as a failed verification.
Securing Missive webhooks
Read X-Hook-Signature, check that it starts with sha256=, and take the rest. Compute the hex HMAC-SHA256 of the raw request body with the validation secret as the key, and compare in constant time, as Missive asks.
const crypto = require('crypto');
const express = require('express');
function verifyMissiveSignature(rawBody, signatureHeader, secret) {
if (!signatureHeader || !secret || !signatureHeader.startsWith('sha256=')) return false;
const received = Buffer.from(signatureHeader.slice('sha256='.length).toLowerCase(), 'utf8');
const expected = Buffer.from(
crypto.createHmac('sha256', secret).update(rawBody).digest('hex'),
'utf8'
);
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
const app = express();
app.post('/webhooks/missive', express.raw({ type: '*/*' }), (req, res) => {
if (!verifyMissiveSignature(req.body, req.get('x-hook-signature'), process.env.MISSIVE_WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
const payload = JSON.parse(req.body.toString('utf8'));
// Route on payload.rule.id or payload.rule.type, then queue the work.
res.sendStatus(200);
});
The same check in Python:
import hashlib
import hmac
def verify_missive_signature(raw_body: bytes, signature_header, secret: str) -> bool:
if not signature_header or not secret or not signature_header.startswith("sha256="):
return False
received = signature_header[len("sha256="):].lower()
expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(received.encode("utf-8"), expected.encode("utf-8"))
# In FastAPI: raw_body = await request.body()
# verify_missive_signature(raw_body, request.headers.get("x-hook-signature"),
# os.environ["MISSIVE_WEBHOOK_SECRET"])
Missive signs only the body and sends no timestamp, so there is no replay window to check.
Make Missive webhooks production-ready. Hookdeck Event Gateway verifies the
X-Hook-Signatureheader on every delivery, answers well inside the 15-second window, and holds each conversation event until your service has processed it.
Missive webhook limitations and pain points
Retries are over in eight minutes
The Problem: A short outage on your side loses events for good.
Why It Happens: Missive retries an error or a timeout up to 5 times over 8 minutes. A deploy, a database failover or a slow dependency can easily last longer.
Workarounds:
- Keep the webhook route simple and return a
2xxas soon as the request is verified and stored. - After an outage, use the API or the conversations themselves to catch up on what you missed.
How Hookdeck Can Help: Hookdeck accepts Missive's deliveries as soon as they arrive, so the eight minutes aren't spent on your downtime. Events wait in Hookdeck and are delivered with retries you configure.
50 failures in a row disable the rule
The Problem: Your integration goes quiet, and the rule shows as disabled.
Why It Happens: If a webhook rule fails more than 50 times in a row, Missive disables it. A busy inbox can produce 50 failures quickly.
Workarounds:
- Check the Rules settings after any incident and re-enable the rule.
- Turn on Show rule webhook events to confirm whether Missive fired the webhook for a conversation.
How Hookdeck Can Help: Because Hookdeck answers Missive straight away, failures in your service don't count against the rule, and Hookdeck's issues alert you to the failures instead.
Unsigned unless you set a secret
The Problem: Anyone who learns the URL can post fake conversation data.
Why It Happens: The validation secret is optional, and without it Missive sends no signature.
Workarounds:
- Set a validation secret on every webhook action.
- Reject requests that lack
X-Hook-Signaturewhen you expect one.
How Hookdeck Can Help: Hookdeck's Missive source verifies every request and marks unsigned ones as failed verification, which shows you any rule set up without a secret.
No event catalogue or delivery ID
The Problem: You can't list every rule.type value in advance, and you can't tell a retry from a new event.
Why It Happens: Missive documents rule types by their labels in the app, with only two wire values in its examples, and the payload has no delivery ID.
Workarounds:
- Route on
rule.id, which you control, and log unknownrule.typevalues. - Build an idempotency key from
rule.idandlatest_message.id.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeats based on the body fields you choose, such as rule.id and latest_message.id.
Best practices
Verify against the raw body
Missive signs the full request body. Use express.raw({ type: '*/*' }) in Express, await request.text() in Next.js and await request.body() in FastAPI.
Have the endpoint ready before saving the rule
Missive sends a validation request on save. Deploy the receiver first, or point the rule at Hookdeck, so the save succeeds.
Use one rule per job
Give each integration its own rule and route on rule.id. It keeps conditions simple and lets you disable one integration without affecting the others.
Acknowledge within 15 seconds
Verify, store the payload, return 200, and run the CRM or ticketing work in a background job, as Missive recommends. See why to process webhooks asynchronously and our guide to webhook idempotency.
Conclusion
Missive webhooks are rule actions that post the rule, the conversation and its latest message to your URL. Set a validation secret so each request carries X-Hook-Signature, verify the sha256= hex HMAC against the raw body, route on rule.id, and answer within 15 seconds, because retries end after eight minutes and 50 failures in a row disable the rule.
Hookdeck Event Gateway verifies every Missive signature and keeps each event until your service has handled it, so a deploy or an outage doesn't cost you conversations.
Get started with Hookdeck for free and handle Missive webhooks reliably in minutes.