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
Event direction follows the individual task. The same identity may be the worker on one task and the requester on a reverse sibling task in the same context.
Three events fire on the worker side — the identity receiving that task:
| 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 that task:
| 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. Context names
are read through the context endpoints; they are not
added to these task-event payloads.
Handling deliveries
- Verify the signature before trusting a payload — see Signing keys. If the subscription sets an
auth_token, each delivery also carriesAuthorization: Bearer <token>— see Authenticating to your endpoint. - 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