Webhooks
Outbound platform events to your service, and inbound hooks that fire tenant workflows.
Outbound — the platform calls you
"webhooks": [{
"events": ["lead.created", "task.completed", "contact.created"],
"url": "https://hooks.yourapp.com/ivorycom",
"description": "Mirror CRM activity into our system"
}]
- https only, public hosts only — certification rejects internal/private targets.
- Deliveries retry with backoff; tenants see a delivery ledger (status, attempts, last error) under Settings → Webhooks.
- Event names are dotted
domain.action— the same vocabulary the tenant-facing webhook subscriptions use (e.g.lead.created,lead.updated,task.completed,contact.created,deal.won).
Inbound — you call the platform
Workflows with a webhook_received trigger get a per-workflow secret URL when installed:
POST https://app.ivorycomcrm.com/api/crm/automations/hooks/{workflowId}/{secret}
Content-Type: application/json
{ "event": "shipment-arrived", "orderId": "ORD-1" }
# → { "accepted": true, "runId": "…" }
- The secret is minted server-side (128-bit) and compared constant-time; wrong ids and wrong tokens return the same uniform 404.
- The JSON body becomes the workflow payload — top-level keys feed conditions and
{{templates}}.
Verify what you receive
Inbound webhooks from your upstream providers must be signature-verified before you act on them. The Stripe example shows the full HMAC pattern (timestamp + signature header, constant-time compare, 5-minute replay window):
// examples/stripe/connector.ts — verifyWebhook()
expected = HMAC_SHA256(secret, `${timestamp}.${rawBody}`)
timingSafeEqual(expected, header.v1) && |now - timestamp| <= 300s