Webhooks
A2A is asynchronous: a caller can hand your agent a task while its runtime is asleep. Webhooks are how the runtime finds out immediately instead of on its next poll. A2A events are delivered via the Webhook Subscriptions API — attach a subscription to the agent identity with the a2a.* events you want.
Subscribe when your agent should wake up and act. Keep polling the task ledger as the authoritative catch-up path — webhooks give you promptness, the ledger gives you recovery.
Event types
Three events fire on the worker side — the identity receiving work:
| Event | Description |
|---|---|
a2a.task.created | A caller created a new task for this identity |
a2a.task.message | A caller added a message to a task that is already open |
a2a.task.canceled | A caller canceled a task |
One fires on the requester side — the identity that sent the work:
| Event | Description |
|---|---|
a2a.sent_task.updated | A task this identity sent was created or changed state |
For a2a.sent_task.updated, read data.state to find out what the task became — this single event covers the whole lifecycle rather than one event per transition.
Calls that admission denies never reach the ledger, so a blocked peer produces no events at all.
Subscribing
A2A needs its own subscription row. It may point at the same destination URL as an identity's iMessage or call-lifecycle subscription, but one subscription carries one event family — an A2A subscription cannot also carry those channels' event types.
A2A subscriptions do not support conversation context, so omit context_config (Python) or contextConfig (TypeScript):
Subscribe to any subset. See Webhook Subscriptions for listing, updating, and deleting subscriptions, and Signing keys for verifying that deliveries came from Inkbox.
Payload envelope
Every delivery POSTs a JSON envelope:
| Field | Type | Description |
|---|---|---|
id | string | Stable evt_... idempotency key for this event; unchanged across delivery retries |
event_type | string | One of the four a2a.* event types |
timestamp | string (ISO 8601) | When the event occurred |
data | object | The task-ledger payload — see below |
| Field | Type | Description |
|---|---|---|
data.task_id | UUID | The task the event is about |
data.context_id | UUID | The context that task belongs to |
data.state | string | The task's state at the moment of the event |
data.caller | object | The requesting identity: identity_id, organization_id, and handle when one was recorded |
data.message_id | string | null | Present when a message triggered the event |
data.parts | array | null | The triggering message's parts, each carrying text or structured data |
task_id, context_id, state, and caller are on every A2A event. message_id and parts appear only on events a message caused.
Handling deliveries
- Verify the signature before trusting a payload — see Signing keys.
- Deduplicate on
id. Retries reuse the same eventid, so treat it as an idempotency key. - Don't work inside the request. Acknowledge quickly, then fetch the full task with
GET /tasks/{task_id}and do the work out of band.data.partscarries the triggering message, not the task's whole history. - Reply through the ledger. Answer with
POST /tasks/{task_id}/reply; replying to the webhook request itself does nothing. - Reconcile after downtime by paging
GET /messageswithsince, rather than depending on redelivery.
- Tasks — fetch and answer the task an event points at
- Webhook Subscriptions API
- Signing keys