Guide to Intercom Webhooks: Features and Best Practices
Intercom webhooks notify your application when conversations, contacts, and tickets change. If you're routing support events, syncing contacts to a CRM, or mirroring tickets, webhooks are how you react without polling.
This guide covers how Intercom webhooks work, the topics you'll subscribe to, how to verify the X-Hub-Signature (it's SHA-1), and the ping handshake.
What are Intercom webhooks?
Intercom webhooks are JSON POSTs delivered to a URL you configure in the Developer Hub. Each is signed with an X-Hub-Signature header formatted sha1=<hex>. The signature is an HMAC-SHA1 (a legacy scheme, not the modern SHA-256) over the raw request body, keyed with your app's client_secret, not a separate webhook secret and not the access token.
Intercom webhook features
| Feature | Details |
|---|---|
| Configuration | Developer Hub > your app > Webhooks; subscribe to topics |
| Signature header | X-Hub-Signature, formatted sha1=<hex> |
| Signature scheme | HMAC-SHA1 (hex) over the raw body, keyed with the app client_secret |
| Setup handshake | A signed ping on save; verify it and return 2xx |
| Idempotency | Dedupe on notification.id |
| SDK | None for verification; verify manually |
Common events
Intercom calls its events topics. A representative set:
| Topic | Fires when |
|---|---|
ping | Intercom validates the endpoint on save |
conversation.user.created / conversation.user.replied | A user starts or replies to a conversation |
conversation.admin.replied / .assigned / .closed / .noted | An admin acts on a conversation |
contact.user.created / contact.lead.created | A contact or lead is created |
contact.user.tag.created | A contact is tagged |
ticket.created / ticket.admin.assigned / ticket.state.updated | A ticket changes |
This isn't exhaustive; subscribe to the topics your use case needs in the Developer Hub.
See Intercom webhook payloads in action. Inspect and replay sample Intercom webhook payloads in the Hookdeck Console — no account or setup required.
Setting up Intercom webhooks
In the Developer Hub, open your app > Webhooks, set the endpoint URL, choose the API version, and select topics. Get the signing key from Basic Information > Client secret and store it as INTERCOM_CLIENT_SECRET. Rotating the client secret invalidates signatures for all webhooks, so rotate and update the env in lockstep.
Securing Intercom webhooks
Split the X-Hub-Signature on =, confirm the algorithm is sha1, compute an HMAC-SHA1 over the raw body with your client_secret, hex-encode it, and compare in constant time. Verify against the raw body before parsing.
const crypto = require("crypto");
const CLIENT_SECRET = process.env.INTERCOM_CLIENT_SECRET;
function verify(rawBody, signatureHeader) {
if (!signatureHeader) return false;
const [algorithm, signature] = signatureHeader.split("=");
if (algorithm !== "sha1" || !signature) return false;
const expected = crypto.createHmac("sha1", CLIENT_SECRET).update(rawBody).digest("hex");
try {
return crypto.timingSafeEqual(Buffer.from(signature, "hex"), Buffer.from(expected, "hex"));
} catch {
return false;
}
}
app.post("/webhooks/intercom", express.raw({ type: "application/json" }), (req, res) => {
if (!verify(req.body, req.headers["x-hub-signature"])) {
return res.sendStatus(401);
}
const payload = JSON.parse(req.body.toString());
if (payload.topic === "ping") return res.sendStatus(200); // handshake
res.sendStatus(200); // acknowledge fast
processQueue.add(payload); // dedupe on notification.id, async
});
The same check in Python:
import hashlib
import hmac
import os
CLIENT_SECRET = os.environ["INTERCOM_CLIENT_SECRET"].encode()
def verify(raw_body: bytes, signature_header: str) -> bool:
if not signature_header:
return False
try:
algorithm, signature = signature_header.split("=", 1)
except ValueError:
return False
if algorithm != "sha1" or not signature:
return False
expected = hmac.new(CLIENT_SECRET, raw_body, hashlib.sha1).hexdigest()
return hmac.compare_digest(signature, expected)
Make Intercom webhooks production-ready. Hookdeck Event Gateway verifies the
X-Hub-Signature, deduplicates, and durably queues every event.
Intercom webhook limitations and pain points
It's SHA-1, not SHA-256
The Problem: X-Hub-Signature is HMAC-SHA1, the legacy scheme (not GitHub's modern X-Hub-Signature-256). Using SHA-256 never matches.
Why It Happens: Intercom uses the older SHA-1 X-Hub-Signature.
Workarounds:
- Use SHA-1, hex, over the raw body, and strip the
sha1=prefix.
How Hookdeck Can Help: Hookdeck verifies the signature at the edge with the right algorithm, so your app doesn't hard-code SHA-1.
The key is the app client_secret
The Problem: There's no separate webhook signing secret. Signatures are keyed with the app's client_secret, and using the access token (a common mix-up) fails.
Why It Happens: Intercom reuses the app client secret for webhook signing.
Workarounds:
- Key the HMAC with
client_secretfrom Basic Information, not the access token.
How Hookdeck Can Help: Hookdeck holds the secret and verifies centrally, so the right key is used in one place.
The ping handshake
The Problem: On save, Intercom sends a signed ping. A handler that tries to process it as a data event, or that skips verification for it, mishandles the setup check.
Why It Happens: Intercom validates the endpoint with a ping when you save the webhook.
Workarounds:
- Verify the ping like any delivery, then return 2xx without treating it as a data event.
How Hookdeck Can Help: Hookdeck can accept the ping and forward only real events to your handler.
No timestamp, so dedupe on notification.id
The Problem: There's no timestamp or replay window, and retries can redeliver the same event.
Why It Happens: Intercom's scheme has no timestamp; delivery is at-least-once.
Workarounds:
- Dedupe on
notification.idand make handlers idempotent.
How Hookdeck Can Help: Hookdeck deduplicates deliveries at the edge. See our guide to webhook idempotency.
Best practices
Verify HMAC-SHA1 over the raw body with client_secret
Confirm the sha1= prefix, compute the hex HMAC-SHA1 over the raw body, and compare in constant time.
Handle the ping
Verify the ping and return 2xx so the webhook saves, without processing it as data.
Dedupe on notification.id
Retries can redeliver, so make handlers idempotent.
Acknowledge fast, process asynchronously
Return 2xx quickly (Intercom expects a fast response) and defer work to a queue. See why to process webhooks asynchronously.
Conclusion
Intercom webhooks are verified with an X-Hub-Signature HMAC-SHA1 over the raw body, formatted sha1=<hex> and keyed with your app's client_secret. Handle the signed ping on save, verify over the raw body, dedupe on notification.id, and acknowledge fast.
Hookdeck verifies the signature, handles the ping, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique events.
Get started with Hookdeck for free and handle Intercom webhooks reliably in minutes.