Webhooks
Webhooks let external systems react to events in BottleCRM in near real time. Configure an endpoint URL in Settings → Integrations → Webhooks, and BottleCRM will POST a signed JSON payload whenever the subscribed events fire.
Events
| Event | Fired when |
|---|---|
lead.created |
A lead is created (any source) |
lead.converted |
A lead is converted to an account/contact/opportunity |
opportunity.stage_changed |
An opportunity moves between pipeline stages |
opportunity.won |
An opportunity is closed as won |
case.created |
A new support ticket is opened |
case.escalated |
A ticket is escalated past its SLA |
invoice.sent |
An invoice is emailed to a customer |
invoice.paid |
An invoice is marked as paid |
Payload format
{
"id": "evt_1a2b3c…",
"type": "lead.converted",
"created_at": "2026-04-12T15:32:11Z",
"org_id": "ab12-cd34-…",
"data": {
"lead": { "id": "…", "title": "…", "email": "…" },
"account": { "id": "…", "name": "…" },
"contact": { "id": "…", "email": "…" },
"opportunity": { "id": "…", "name": "…", "amount": 24000 }
}
}
idis unique per event — use it for idempotency on your side.org_idlets multi-tenant consumers route the event correctly.datais the same shape as the corresponding API resource(s) at the moment the event fired.
Verifying signatures
Every request includes an X-BottleCRM-Signature header containing an HMAC-SHA256 of the raw request body, keyed by the webhook's signing secret (shown once when you create the webhook):
X-BottleCRM-Signature: sha256=8f8b7c…
A Python verifier:
import hmac, hashlib
def verify(body_bytes: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), body_bytes, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, header)
A Node.js verifier:
import crypto from "node:crypto";
export function verify(body, header, secret) {
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected), Buffer.from(header)
);
}
Delivery semantics
- At-least-once — BottleCRM may retry a delivery on
5xxor timeout. De-duplicate on theidfield. - Retries — exponential backoff at 1m, 5m, 30m, 2h, 6h. After the final failure the event is parked and surfaced in the webhook's Recent deliveries panel for manual re-send.
- Timeout — your endpoint must respond within 10 seconds. Do heavy work in a background job.
- Order — events are dispatched in the order they fire but parallel deliveries may arrive out of order. Use timestamps if order matters.
Testing locally
The webhook UI has a Send test event button that delivers a synthetic payload for any subscribed type. For development against a local backend, use a tunnel (ngrok, Cloudflare Tunnel) to expose your handler to the running CRM.