---
title: "The two legs were joined. This is the billable outcome. occurred_at is when we RECORDED the join, after webhook queue lag, not the carrier-asserted instant the audio paths met."
method: POST
path: "bridge.bridged"
tags: ["webhooks"]
---

# The two legs were joined. This is the billable outcome. occurred_at is when we RECORDED the join, after webhook queue lag, not the carrier-asserted instant the audio paths met.

`POST bridge.bridged` (webhook)

Sent when a session enters one of: bridged. Authenticate it with the HMAC signature described on BridgeWebhookEvent before trusting any field. Respond 2xx as soon as you have durably accepted it; any non-2xx, or a response slower than the per-attempt timeout, is treated as a failure and retried.

## Payload

- BridgeWebhookEvent: The signed JSON body of an outbound bridge notification. **SIGNATURE, verify before you parse.** Every POST carries `x-corebee-signature`: the lowercase hex HMAC-SHA256 of the EXACT raw request body bytes, keyed by the shared secret we issued you. Recompute it over the raw bytes, not over a re-serialization of the parsed JSON, key order and whitespace are part of what was signed, and any JSON library that reorders keys will produce a digest that does not match. Compare with a constant-time equality function. A second header, `x-corebee-event-type`, repeats `type` for routing; it is NOT part of the signed string, so never make a trust decision on it. ⛔ **The signed string contains no timestamp**, so the signature alone does not prove freshness: a captured request stays verifiable forever. Until that is fixed, dedupe on `event_id` and treat an event whose `session_id` you have already seen in a terminal state as a replay, with one exception: a bridge.bridged or bridge.completed for a session you recorded as bridge.failed with failure_reason business_abandoned_bridge is a late-join correction (see bridge.completed), and is sent at most once per session. We consider this a gap in our scheme and not something you should design around permanently. **Ordering and duplicates.** Delivery is at-least-once. `event_id` is stable and unique per transition, dedupe on it. `previous_state` lets you detect an out-of-order arrival: if it does not match the state you last recorded for the session, you received these two out of order. Retries are exponential with a bounded budget per attempt-cycle, and a notification that cannot be delivered inline is retried from a durable outbox, so a slow or briefly-down endpoint does not lose the event. Only the fields below are sent. The session metadata bag is never forwarded: it holds internal orchestration keys, so `customer_ref` is the one value of yours that comes back.
  - `event_id` string, required: Stable and unique per transition; the idempotency key you dedupe on. Format `vbe_{session_id}_{n}`. In the rare case where the underlying event row could not be persisted we still deliver terminal notices, with the suffix replaced by `s-{state}`, so dedupe on the whole string, and fall back to (session_id, state) if you see that form.
  - `session_id` integer, required
  - `type` 'bridge.dialing' | 'bridge.human_detected' | 'bridge.bridged' | 'bridge.completed' | 'bridge.failed', required: Semantic event type. CLOSED set: these five and no others.
  - `state` 'created' | 'dialing_business' | 'ivr_nav' | 'amd_verdict' | 'human' | 'voicemail' | 'prep_line' | 'converse' | 'dialing_consumer' | 'bridging' | 'bridged' | 'completed' | 'failed', required: The state just entered. In practice only the delivered states appear here (dialing_business, dialing_consumer, human, bridged, completed, failed); the enum is the full roster so that this field and the one on BridgeStatus deserialize identically. These states are never delivered as a webhook: created, ivr_nav, amd_verdict, voicemail, prep_line, converse, bridging.
  - `previous_state` 'created' | 'dialing_business' | 'ivr_nav' | 'amd_verdict' | 'human' | 'voicemail' | 'prep_line' | 'converse' | 'dialing_consumer' | 'bridging' | 'bridged' | 'completed' | 'failed', required: The state we came from. Use it to detect out-of-order arrivals.
  - `amd_result` string, nullable, required: Answering-machine detection verdict when one had been reached, else null.
  - `failure_reason` string, nullable, required: Populated on bridge.failed, null otherwise. An OPEN string, exactly as on BridgeStatus: new reasons are added without a version bump, so do not validate it against a closed list.
  - `customer_ref` string, nullable, required: Your correlation id from metadata.customer_ref at create time, echoed back. Null when you did not send one.
  - `occurred_at` string, date-time, required: When we recorded the transition. This is our clock after webhook processing, not the carrier's clock at the moment the event happened, and it can trail the real instant by seconds under queue lag. Do not difference two of these to compute a call duration.

## Acknowledgement `200`

Accepted. Any 2xx is treated as success.

---

[API](https://skmtc.dev/corebee/apis/corebee-api.md) · [All operations](https://skmtc.dev/corebee/apis/corebee-api/llms.txt) · [OpenAPI document](https://skmtc.dev/corebee/apis/corebee-api/revisions/a48ea543b4dc?raw)
