Call Event Webhooks
Breesy can push voice-agent call events to your systems as they happen, so you don’t need to poll the Data Share API on a schedule. Each webhook subscription is franchise-scoped, delivers signed JSON payloads over HTTPS, and retries automatically for up to 24 hours.
Subscriptions
A subscription is an HTTPS endpoint plus the set of events it should receive. Subscriptions are managed by your Breesy administrator (Breesy admin or franchise Power Admin) from within Breesy. Each subscription has:
| Field | Description |
|---|---|
url | Your HTTPS endpoint. Breesy sends a POST request per event. |
events | The event types to deliver (defaults to all). |
secret | A signing secret generated when the subscription is created. It is shown once — store it securely. |
active | Inactive subscriptions receive no deliveries. |
No-code tools work too: paste the webhook URL that Zapier or Make gives you, and every completed call arrives as a ready-to-use JSON payload.
Event Types
| Event | Fires when |
|---|---|
call.completed | A voice-agent call has ended and post-call processing has finished |
call.completed fires once per call, as soon as post-call processing finishes — when the transcript, summary, categorization, and recording link are all available. A single delivery carries everything, so no follow-up fetch is needed.
Request Format
Every delivery is a POST with Content-Type: application/json and these headers:
| Header | Description |
|---|---|
X-Breesy-Event | The event type, e.g. call.completed |
X-Breesy-Delivery-Id | Unique ID for this delivery. The same ID is reused on retries — use it for idempotency. |
X-Breesy-Signature | sha256=<hex> HMAC signature of the raw request body |
Example Payload
{ "event": "call.completed", "delivery_id": "7f0d3c62-1111-2222-3333-444455556666", "occurred_at": "2026-06-22T18:04:11.000Z", "conversation_id": "CA0123456789abcdef0123456789abcdef", "franchise_id": "0c8b2a1e-1111-2222-3333-444455556666", "call": { "conversation_id": "CA0123456789abcdef0123456789abcdef", "caller_id": "+15551234567", "timestamp": "2026-06-22T18:04:11+00:00", "duration": "182", "agent_type": "afterhours", "call_category": "new_urgent_service_request", "customer_name": "Jordan Smith", "customer_email": "jordan@example.com", "call_summary": "Water damage in the basement after a pipe burst.", "loss_type": "Water", "referral_source": "Google", "notes": "Caller requested a morning callback.", "property_area": "Basement", "request_type": "INITIAL_SERVICE_REQUEST", "service_location": "123 Main St, Springfield", "callback_number": "+15557654321", "location_id": "aaaa1111-2222-3333-4444-555566667777", "location": "North Branch", "transcript": "Agent: Thanks for calling...", "real_estate": { "address": "123 Main St, Springfield", "last_sale_value": "415000", "current_value": "468000", "owner_name1": "Jordan Smith", "owner_name2": null, "room_count": "7", "square_footage": "2100", "year_built": "1998" }, "recording_url": "https://api.breesy.app/data/recordings/CA0123456789abcdef0123456789abcdef", "recording_breesy_url": "https://www.breesy.app/insights?tab=calldata&callId=conv_CA0123456789abcdef0123456789abcdef" }}The call object uses the same shape as the GET /data/calls endpoint. It reflects the call’s state at send time, not at the moment the event occurred, so a retried delivery may contain newer call data than the original attempt. recording_url is the stable Breesy recording link — it never expires from your side and requires your Data Share API key when fetched.
Verifying Signatures
Each request body is signed with HMAC SHA-256 using your subscription’s secret. Always verify the signature before trusting a payload.
import crypto from "node:crypto";
function verifyBreesySignature(rawBody, signatureHeader, secret) { const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex"); return crypto.timingSafeEqual( Buffer.from(signatureHeader), Buffer.from(expected), );}
// Express example — use the raw (unparsed) request bodyapp.post("/webhooks/breesy", express.raw({ type: "application/json" }), (req, res) => { if (!verifyBreesySignature(req.body, req.get("X-Breesy-Signature"), process.env.BREESY_WEBHOOK_SECRET)) { return res.status(401).send("invalid signature"); } const event = JSON.parse(req.body); // ... process event ... res.status(200).send("ok");});Compute the HMAC over the raw request body bytes — re-serializing the parsed JSON can change key ordering or whitespace and produce a different signature.
Responding to Deliveries
Respond with any 2xx status within 10 seconds to acknowledge a delivery. Anything else (non-2xx, timeout, connection error) counts as a failed attempt and the delivery is retried.
Process asynchronously if your handling is slow: acknowledge immediately, then do the work.
Retry Schedule
Failed deliveries are retried with exponential backoff — up to 8 total attempts, with minimum delays between attempts:
| Attempt | Minimum delay after previous failure |
|---|---|
| 1 | Immediately after post-call processing finishes |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 15 minutes |
| 5 | 1 hour |
| 6 | 3 hours |
| 7 | 6 hours |
| 8 | 12 hours |
Dispatch is event-driven: a retry that has become due is sent the next time the dispatcher runs (triggered by any subsequent call event, or by a manual redelivery initiated by a Breesy administrator), so the actual delay can be longer than the minimum during quiet periods.
After the final attempt fails, the delivery is marked failed and will not be retried automatically. Your Breesy administrator can view recent deliveries (including failures and the last error) and delivery success-rate stats inside Breesy.
Delivery Guarantees
- At-least-once delivery. A delivery may occasionally be sent more than once (for example, if your endpoint responds slowly and the acknowledgement is missed). Deduplicate using
X-Breesy-Delivery-Id. - No strict ordering. Retries can cause a later call’s event to arrive before an earlier one. Use the call’s
timestamprather than arrival order. - Reconciliation. Webhooks are best-effort by nature. If you need a guaranteed complete record, periodically reconcile against
GET /data/callsusing a date range.