Webhooks
At a glance
| Location | Sidebar → Webhooks (ClipboardList icon) |
| Plan | All plans, including the free tier |
| Related features | 2 |
Connect Spun to external services via webhooks. Trigger actions in other tools when events happen in your inbox. Automation tools (n8n, Zapier, Make) can also manage webhook subscriptions programmatically through the REST API: an API key holding the manage_webhooks scope can create, list, update and delete subscriptions on /api/integrations/webhooks, so a workflow tool can wire up its own trigger without opening Spun. Message events (message.inbound and message.outbound) carry everything a helpdesk or CRM needs to file the message without a second lookup: the other party's phone in E.164 (peer_phone_e164), the matched Spun contact (contact_id, contact_name, contact_email, null when no contact exists yet), a group flag (is_group), and for media messages a signed download link (media_url) that works without a Spun login and expires after 24 hours, plus media_mime, media_filename, media_size and the caption. If the file was still being stored when the event fired, media_url is null and media_pending is true; GET /api/integrations/messages returns a fresh link for that message. The HMAC signing secret is generated by Spun and shown exactly once, when the subscription is created - it is never included in listings. Each delivery attempt draws from the org's AI/automation token budget; if that budget is exhausted, the delivery is skipped instead of sent, and the skip is recorded in the webhook's delivery history alongside normal successes and failures so it stays visible. A subscription can also be told to receive less: "Only these numbers" limits message and conversation events to chats with the listed phone numbers (international format with +, up to 200), and "Direct chats only" skips group chats, newsletters and status updates. Filtered events are decided before delivery, so they are not sent, not logged and not charged, and the webhook card shows how many were filtered; contact and label events are never affected. The same two filters are available through the API as filter_peer_phones and filter_direct_only when creating or updating a subscription. Chats where WhatsApp hides the customer number behind a privacy ID (a chat_id ending in @lid) are delivered too: peer_lid carries the privacy ID digits (not a phone number), identity_state says phone, lid_linked (Spun knows the number, so peer_phone_e164 holds it) or lid_unlinked (peer_phone_e164 is null), and because "Only these numbers" compares phone numbers, such a chat with no known number never matches it. Conversation lifecycle events are available too: conversation.closed fires when a chat is archived, and conversation.reopened fires when an archived chat is reopened in Spun, by an AI app, or through the API, and also when a chat is archived or unarchived on the phone itself (closed_by / reopened_by is then whatsapp:phone, and a chat deleted on the phone arrives as conversation.closed with reason deleted); both carry the chat_id, a reason, who did it (closed_by / reopened_by, for example api: followed by the key name) and the time, and fire only after WhatsApp accepted the change. In the Delivery Log, 200 means your endpoint accepted the delivery. If it processes the event afterwards, check its own log for failures: a receiver that answers first and works afterwards (for example an n8n workflow that responds immediately) can still fail after Spun recorded the 200, and Spun cannot see that failure.
How to use
- Open Webhooks from the sidebar.
- Click "+ New Webhook".
- Configure the URL and events to listen for.
- Test the webhook. (Or, from an automation tool: create an API key with the manage_webhooks scope and call POST /api/integrations/webhooks with a URL and event list.)
Related features
FAQ
- How do I set up webhooks?
- Can I integrate with other tools?
- What events can I hook into?
- Why did a webhook delivery get skipped?
- Can my webhook ignore group chats?
- How do I send only some numbers to my helpdesk?