Broadcast events
Events emitted as a broadcast recipient's delivery status changes across any channel (email, sms, whatsapp, push, voice).
See Outgoing webhooks for how delivery, retries and the common event / event_id fields work.
Delivery lifecycle
A single recipient typically produces two events:
broadcast.sent, fired the moment Bunce attempts the delivery.- A terminal event (
broadcast.delivered,broadcast.failed,broadcast.not_contacted, or an engagement event likebroadcast.opened/broadcast.clicked) reflecting the real outcome.
The broadcast.sent event records the attempt, the terminal event records the result.
Common fields
All payloads below are shown in full. These fields appear on every broadcast event:
| Field | Type | Notes |
|---|---|---|
event | string | Event name, one of the values below. |
event_id | string | Unique id for this event. |
broadcast_id | string | Id of the broadcast this event belongs to. |
subject | string | Broadcast subject / title. |
timestamp | string (ISO 8601) | When the status occurred (action_at). |
channel | string | Delivery channel: email, sms, whatsapp, push, or voice. |
page_count | int | Number of pages in the broadcast. |
sender.name | string | Configured sender name. |
sender.email | string | null | Sender email; null on non-email channels. |
recipient.id | string | Internal company-customer id. |
recipient.first_name | string | Recipient first name. |
recipient.last_name | string | Recipient last name. |
recipient.email | string | null | Recipient email. |
recipient.phone | string | null | Recipient phone number. |
recipient.company_customer_id | string | External / business customer id. |
Depending on the channel, either email or phone will be the relevant contact field; the other may be null.
Events
broadcast.sent
The delivery was attempted / handed to the provider.
{
"event": "broadcast.sent",
"event_id": "01j8z6f3q2m9r7t1v4x5y6z7a8",
"broadcast_id": "01j8y9k2m3n4p5q6r7s8t9u0v1",
"subject": "October promo",
"timestamp": "2026-10-13T09:30:00.000000Z",
"channel": "email",
"page_count": 1,
"sender": {
"name": "Acme Store",
"email": "news@acme.test"
},
"recipient": {
"id": "01j8x1y2z3a4b5c6d7e8f9g0h1",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.test",
"phone": "+2348012345678",
"company_customer_id": "cust_12345"
}
}broadcast.delivered
The provider confirmed the message reached the recipient.
{
"event": "broadcast.delivered",
"event_id": "01j8z6f3q2m9r7t1v4x5y6z7b9",
"broadcast_id": "01j8y9k2m3n4p5q6r7s8t9u0v1",
"subject": "October promo",
"timestamp": "2026-10-13T09:30:05.000000Z",
"channel": "email",
"page_count": 1,
"sender": {
"name": "Acme Store",
"email": "news@acme.test"
},
"recipient": {
"id": "01j8x1y2z3a4b5c6d7e8f9g0h1",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.test",
"phone": "+2348012345678",
"company_customer_id": "cust_12345"
}
}broadcast.opened
The recipient opened the message (open tracking).
{
"event": "broadcast.opened",
"event_id": "01j8z6f3q2m9r7t1v4x5y6z7c0",
"broadcast_id": "01j8y9k2m3n4p5q6r7s8t9u0v1",
"subject": "October promo",
"timestamp": "2026-10-13T09:41:12.000000Z",
"channel": "email",
"page_count": 1,
"sender": {
"name": "Acme Store",
"email": "news@acme.test"
},
"recipient": {
"id": "01j8x1y2z3a4b5c6d7e8f9g0h1",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.test",
"phone": "+2348012345678",
"company_customer_id": "cust_12345"
}
}broadcast.clicked
The recipient clicked a tracked link. Adds url.
| Field | Type | Notes |
|---|---|---|
url | string | The link the recipient clicked. |
{
"event": "broadcast.clicked",
"event_id": "01j8z6f3q2m9r7t1v4x5y6z7d1",
"broadcast_id": "01j8y9k2m3n4p5q6r7s8t9u0v1",
"subject": "October promo",
"timestamp": "2026-10-13T09:43:20.000000Z",
"channel": "email",
"page_count": 1,
"sender": {
"name": "Acme Store",
"email": "news@acme.test"
},
"recipient": {
"id": "01j8x1y2z3a4b5c6d7e8f9g0h1",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.test",
"phone": "+2348012345678",
"company_customer_id": "cust_12345"
},
"url": "https://acme.test/offer"
}broadcast.failed
The delivery failed. Adds failure_reason.
| Field | Type | Notes |
|---|---|---|
failure_reason | string | null | Provider or system message describing the failure. |
{
"event": "broadcast.failed",
"event_id": "01j8z6f3q2m9r7t1v4x5y6z7e2",
"broadcast_id": "01j8y9k2m3n4p5q6r7s8t9u0v1",
"subject": "October promo",
"timestamp": "2026-10-13T09:30:08.000000Z",
"channel": "sms",
"page_count": 1,
"sender": {
"name": "Acme Store",
"email": null
},
"recipient": {
"id": "01j8x1y2z3a4b5c6d7e8f9g0h1",
"first_name": "Ada",
"last_name": "Lovelace",
"email": null,
"phone": "+2348012345678",
"company_customer_id": "cust_12345"
},
"failure_reason": "Invalid phone number"
}broadcast.not_contacted
The recipient was skipped and never contacted (e.g. unsubscribed, invalid contact details). Adds not_contacted_reason.
| Field | Type | Notes |
|---|---|---|
not_contacted_reason | string | null | Why the recipient was skipped. |
{
"event": "broadcast.not_contacted",
"event_id": "01j8z6f3q2m9r7t1v4x5y6z7f3",
"broadcast_id": "01j8y9k2m3n4p5q6r7s8t9u0v1",
"subject": "October promo",
"timestamp": "2026-10-13T09:30:00.000000Z",
"channel": "email",
"page_count": 1,
"sender": {
"name": "Acme Store",
"email": "news@acme.test"
},
"recipient": {
"id": "01j8x1y2z3a4b5c6d7e8f9g0h1",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.test",
"phone": "+2348012345678",
"company_customer_id": "cust_12345"
},
"not_contacted_reason": "Recipient unsubscribed"
}broadcast.complained
The recipient marked the message as spam / complained (email feedback loops). Adds reason.
| Field | Type | Notes |
|---|---|---|
reason | string | null | Complaint detail from the provider. |
{
"event": "broadcast.complained",
"event_id": "01j8z6f3q2m9r7t1v4x5y6z7g4",
"broadcast_id": "01j8y9k2m3n4p5q6r7s8t9u0v1",
"subject": "October promo",
"timestamp": "2026-10-13T10:02:00.000000Z",
"channel": "email",
"page_count": 1,
"sender": {
"name": "Acme Store",
"email": "news@acme.test"
},
"recipient": {
"id": "01j8x1y2z3a4b5c6d7e8f9g0h1",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.test",
"phone": "+2348012345678",
"company_customer_id": "cust_12345"
},
"reason": "Reported as spam"
}broadcast.unsubscribed
The recipient unsubscribed. Adds reason.
| Field | Type | Notes |
|---|---|---|
reason | string | null | Unsubscribe detail. |
{
"event": "broadcast.unsubscribed",
"event_id": "01j8z6f3q2m9r7t1v4x5y6z7h5",
"broadcast_id": "01j8y9k2m3n4p5q6r7s8t9u0v1",
"subject": "October promo",
"timestamp": "2026-10-13T10:10:00.000000Z",
"channel": "email",
"page_count": 1,
"sender": {
"name": "Acme Store",
"email": "news@acme.test"
},
"recipient": {
"id": "01j8x1y2z3a4b5c6d7e8f9g0h1",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.test",
"phone": "+2348012345678",
"company_customer_id": "cust_12345"
},
"reason": "Unsubscribed via link"
}Event-to-channel availability
| Event | sms | push | voice | ||
|---|---|---|---|---|---|
broadcast.sent | yes | yes | yes | yes | yes |
broadcast.delivered | yes | yes | yes | yes | yes |
broadcast.opened | yes | yes | yes | no | no |
broadcast.clicked | yes | yes | yes | no | no |
broadcast.failed | yes | yes | yes | yes | yes |
broadcast.not_contacted | yes | yes | yes | yes | yes |
broadcast.complained | yes | no | no | no | no |
broadcast.unsubscribed | yes | yes | yes | yes | no |
Not every event fires for every recipient. Engagement events (broadcast.opened, broadcast.clicked, broadcast.complained, broadcast.unsubscribed) depend on tracking being available for that message and on recipient behavior.