AllyOneCRM / Webhooks
Webhooks
Receive CRM events at your system's URL, signed with HMAC-SHA256 — no polling.
Register
POST/v1/webhooks
{
"name": "ERP — production",
"url": "https://erp.yourcompany.com/webhooks/allyone",
"events": ["contact.created", "deal.won", "form.submitted"]
}
Requires the integrations:manage permission (owner and admin by default). The URL must be public: internal, loopback and cloud-metadata addresses are refused. The 201 response carries the secret used for signing — store it in your secrets vault.
Events
| Event | When it fires | data |
|---|---|---|
contact.created | Contact created through the API or the dashboard. | id, email, first_name, tags |
form.submitted | A CRM form was submitted. | form_id, form_name, contact_id, data |
deal.stage_changed | A deal moved to another stage. | deal_id, title, stage_id |
deal.won | A deal was marked as won. | deal_id, title, value |
deal.lost | A deal was marked as lost. | deal_id, title |
contact.updated | Contact changed through the API or the dashboard. | id, fields (changed fields) |
deal.created | A deal was created. | deal_id, title, value, stage_id, contact_id |
survey.responded | A survey (NPS/CSAT) was answered. | survey_id, contact_id, score |
campaign.completed | A campaign finished (end of sending, limit reached, empty audience or manual completion). | campaign_id, name, sent, failed, reason |
journey.enrolled | A contact was enrolled in a journey. | journey_id, contact_id, enrollment_id |
journey.completed | A contact reached the end of a journey. | journey_id, contact_id, enrollment_id |
contact.unsubscribed | Unsubscribe through the link (per channel), "SAIR" on WhatsApp or erasure at the data subject's request (LGPD). | contact_id, channel, reason |
score.rule_fired | A scoring rule was applied to a contact. | rule and contact |
Payload
{
"id": "5b0d2c8e-7f1a-4c3e-9a51-2e6c0b7d4f10",
"event": "deal.won",
"timestamp": "2026-10-01T14:03:11.204Z",
"tenant_id": "4f1c...",
"data": { "deal_id": "9a2e...", "title": "Annual plan — ACME", "value": 18000 }
}
The event type is in the body's event field. Each delivery has its own id (also in the X-AllyOne-Delivery header), the same across every attempt — use it to discard repeats.
Verify the signature
Content-Type: application/json
X-AllyOne-Signature: sha256=<hex>
X-AllyOne-Event: deal.won
X-AllyOne-Delivery: 5b0d2c8e-7f1a-4c3e-9a51-2e6c0b7d4f10
User-Agent: AllyOne-CRM-Webhook/1.0
X-AllyOne-Signature is the HMAC-SHA256 of the raw body with the webhook's secret. Compute it over the bytes received, before parsing the JSON, and compare in constant time:
import crypto from 'node:crypto'
// rawBody: the EXACT body received (Buffer/string), before any JSON.parse
function isValid(rawBody, header, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
const a = Buffer.from(expected), b = Buffer.from(header ?? '')
return a.length === b.length && crypto.timingSafeEqual(a, b)
}
import hmac, hashlib
def is_valid(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header or "")
Delivery and failures
- Success is any
2xxresponse within 10 seconds. Redirects are not followed. - On failure, the CRM retries right away: up to 3 attempts in total, 1 s and 2 s apart. After that there is no further retry.
- Every delivery is recorded in
GET /v1/webhooks/:id/deliveries(the 50 most recent, with HTTP status, attempt count and success) — use it to reconcile what failed. - Respond
2xxquickly and process in a queue on your side: a slow endpoint burns the 3 attempts in seconds.
Manage
| Route | What it does |
|---|---|
GET /v1/webhooks | Lists the tenant's webhooks. |
GET /v1/webhooks/events | Events accepted for subscription. |
PATCH /v1/webhooks/:id | Changes name, url, events or active. |
POST /v1/webhooks/:id/rotate-secret | Generates a new secret (the old one stops working immediately). |
GET /v1/webhooks/:id/deliveries | Delivery history. |
DELETE /v1/webhooks/:id | Removes it. |
