Webhooks
Part of the MountDev Ops Workspace documentation. Updated October 7, 2026
MountDev Ops Workspace documentation | Updated October 7, 2026
Webhooks tell your other systems the moment something changes in your workspace, and let them add records to it. Owners and admins manage them in the workspace menu, Integrations. AI agents never see or change webhooks.
Outbound Webhooks
On the Webhooks tab (action core.webhooks.list), each webhook is an https address that hears about the events you choose.
- Add a Webhook (action
core.webhooks.save): a name, the address, and the events to send. The address must start with https:// and be on the public internet: addresses on private networks (such as 10.x, 192.168.x, or localhost) and link-local addresses are refused, whether written as numbers or reached through a name. - Its secret is shown once when it is added, with Copy. Your receiver uses it to check each delivery. It is kept encrypted and never shown again. Change can replace it, and the new one is shown once.
- Send Test (action
core.webhooks.test) sends one sample event straight away, signed like any other, and shows the answer. - Deliveries (action
core.webhooks.deliveries) shows the newest 50 deliveries: the event, Delivered, Waiting, or Failed, the number of tries, the receiver's last answer, and when the next try is. Send Again (actioncore.webhooks.retry) sends a failed delivery again from the first try. - Turn Off (action
core.webhooks.turn_off) stops new deliveries; Turn On (actioncore.webhooks.turn_on) starts them again. - Delete (action
core.webhooks.delete) deletes the webhook and its secret, and cancels deliveries still waiting.
Events
Every module has three events: a record was added (<module>.created), changed (<module>.updated), or deleted (<module>.deleted), such as crm.contacts.created. Apps add their own:
| Event | When |
|---|---|
helpdesk.ticket.opened | A ticket starts |
helpdesk.ticket.replied | A reply goes out on a ticket |
helpdesk.ticket.client_replied | A client writes on a ticket |
helpdesk.ticket.closed | A ticket is closed |
forms.form.submitted | A website form is sent |
billing.invoice.paid | An invoice is paid in full |
An app's events are offered once the app is added. Changes from people, AI agents, API keys, inbound webhooks, email, and automations all count. Importing records sends no events, because an import can bring in thousands of records at once.
What Is Sent
Each delivery is a POST with a JSON body:
{ "id": "evt_1042", "type": "crm.contacts.updated", "at": "2026-10-07T17:00:00.000Z", "via": "screen", "module": "crm.contacts", "record": { "id": 17, "module_id": "crm.contacts", "data": { "name": "Ann Lee" }, "owner_id": 2, "version": 4, "created_at": "...", "updated_at": "..." }, "changes": { "phone": { "from": null, "to": "555-0100" } } }
changes comes with updates only. record.data holds the fields that are on; for a deleted record, its last values. via says who made the change: screen, agent, api, automation, webhook, email, or system.
Headers: X-MDO-Event (the event), X-MDO-Delivery (the delivery's number, the same on every try), and X-MDO-Signature.
Checking the Signature
X-MDO-Signature looks like t=1791392400,v1=5d41.... To check it, take t, a period, and the raw body, make the HMAC-SHA256 of that with your webhook's secret, and compare it in constant time with v1. Refuse a delivery whose t is more than a few minutes old, so an old delivery cannot be sent to you again.
Tries
Answer with any 2xx status to say a delivery arrived. Anything else (or no answer within 10 seconds) is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, and 12 hours. After the last try it is marked Failed, and the webhook shows it. Redirects are never followed. A delivery may arrive more than once, so use its event id to skip one you already have.
Inbound Webhooks
On the Inbound Webhooks tab (action core.inbound_hooks.list), each inbound webhook is an address another system posts JSON to. Each post becomes a record in the module you choose.
- Add an Inbound Webhook (action
core.inbound_hooks.save): a name, the module its records go in, and which value in the JSON fills each field, by its key, such asemail, orcontact.emailfor a value insidecontact. Fields left empty are skipped. - Its address is shown with Copy. Treat it like a password: anyone with it can add records.
- Values are checked exactly as on the screens. A post that does not fit (a required field missing, a choice that is not one of the field's choices) is refused with the reason, and the webhook shows the last refusal.
- Send a POST with a JSON object of up to 64 KB. The answer is
{ "ok": true, "id": <the new record> }, or{ "error": "..." }. - Turn Off (action
core.inbound_hooks.turn_off) refuses posts until Turn On (actioncore.inbound_hooks.turn_on). Delete (actioncore.inbound_hooks.delete) ends the address for good. Records it added stay.
Records an inbound webhook adds are marked Inbound webhook in their history, and they set off webhooks and automations like any other new record.
