> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# A2A push notifications — subscribe to task lifecycle updates

> Register a webhook URL on an A2A task and receive a signed POST on every lifecycle transition — no polling loop. Covers the capability flag, subscription shapes, the delivery payload, auth options, teardown, and where to see active subscriptions.

# A2A push notifications

A2A push notifications (A2A v1.0 §6.3) replace the poll loop a federating
peer would otherwise run against `tasks/get`. Register a callback URL on the
task when you send it, and every lifecycle transition is POSTed to that URL
the moment the executor advances the task. Use polling and push together, or
either alone — the task ledger records everything regardless.

The [A2A federation reference](/agents/a2a-federation) covers the endpoints
this guide builds on; this guide is the push-specific walkthrough.

## 1. The `pushNotifications` capability flag

Every AgentCard Orbit serves advertises its push capability under
`metadata.capabilities`:

```json theme={null}
{
  "name": "Order Status Agent",
  "metadata": {
    "capabilities": {
      "streaming": false,
      "pushNotifications": true,
      "extendedAgentCard": false
    }
  }
}
```

Fetch the card before you subscribe — see
[Discovery modes](/agents/a2a-federation#discovery-modes) for the card URL
and access policy:

```bash theme={null}
curl -s https://api.orbit.devotel.io/api/v1/agents/agent_abc/.well-known/agent-card.json?tenant=tenant_abc \
  | jq '.metadata.capabilities'
```

`"pushNotifications": true` means the agent accepts a registered
`pushNotification` target on `tasks/send` (REST or JSON-RPC) and delivers
lifecycle callbacks to it. A peer that never checks the flag can still
attempt a subscription — Orbit validates the target at send time and rejects
a malformed one with a 422 — but checking the flag first avoids wasted
envelopes on peers that poll only.

## 2. Subscribe with `pushNotification` on `tasks/send`

Attach a `pushNotification` block to the task-create request. The same block
is accepted on both transports — the REST shorthand and the JSON-RPC
`tasks/send` method — and on the dashboard's outbound-delegation path.

**REST:**

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/agents/agent_abc/a2a/tasks?tenant=tenant_abc \
  -H "Content-Type: application/json" \
  -H "X-A2A-Signature: t=<unix_seconds>,v1=<hmac_hex>" \
  -d '{
    "skill": "order_status",
    "message": {
      "role": "user",
      "parts": [{ "kind": "text", "text": "Status of order ORD-89721" }]
    },
    "pushNotification": {
      "url": "https://you.example.com/a2a/callback",
      "token": "your-callback-bearer-token"
    }
  }'
```

**JSON-RPC:**

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/agents/agent_abc/a2a?tenant=tenant_abc \
  -H "Content-Type: application/json" \
  -H "X-A2A-Signature: t=<unix_seconds>,v1=<hmac_hex>" \
  -d '{
    "jsonrpc": "2.0",
    "id": "req-1",
    "method": "tasks/send",
    "params": {
      "skill": "order_status",
      "message": {
        "role": "user",
        "parts": [{ "kind": "text", "text": "Status of order ORD-89721" }]
      },
      "pushNotification": {
        "url": "https://you.example.com/a2a/callback",
        "token": "your-callback-bearer-token"
      }
    }
  }'
```

The `pushNotification` block has three fields:

| Field     | Required | Notes                                                                                                                                           |
| --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `url`     | yes      | HTTPS only — plain `http:`, localhost, and private/link-local hosts are rejected at send time (422 / `INVALID_PARAMS`).                         |
| `token`   | no       | Bearer credential Orbit sends back as `Authorization: Bearer <token>` on every callback. Max 512 characters.                                    |
| `headers` | no       | Extra static headers merged into every callback POST (custom header names). Use instead of `token` when your endpoint wants a different scheme. |

Subscribing from the dashboard instead of curl: open **Agents → {agent} →
A2A → Delegate to peer** and fill the optional push-URL field — the drawer
forwards the value as the same `pushNotification` block.

Subscribing is per task. A long-lived integration registers the callback on
each task it sends; there is no agent-level default target.

## 3. Webhook endpoint shape

Deliver your callback on any HTTPS endpoint you control. Orbit POSTs a
JSON-RPC-shaped envelope:

```http theme={null}
POST /a2a/callback HTTP/1.1
Host: you.example.com
Content-Type: application/json
Authorization: Bearer your-callback-bearer-token
X-Orbit-A2A-Push-Signature: <detached Ed25519 JWS>

{
  "jsonrpc": "2.0",
  "method": "taskStateUpdate",
  "params": {
    "taskId": "agentA2aTask_9f4c…",
    "status": "completed",
    "output": { "…": "…" }
  }
}
```

| Aspect               | Behaviour                                                                                                                                                                                                                                       |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Trigger statuses** | `completed`, `failed`, `cancelled`. Outbound delegations also push intermediate `working` flips driven by the peer's own callbacks.                                                                                                             |
| **Payload**          | `params.taskId`, `params.status`, `params.output` when the task produced output, `params.error` when it failed.                                                                                                                                 |
| **Headers**          | `content-type: application/json`, plus your `token`/`headers` values verbatim, plus the signature header (next section).                                                                                                                        |
| **Retries**          | None — delivery is best-effort by design, so the task lifecycle never hinges on your endpoint being up. A failed delivery is logged on the Orbit side; poll `tasks/get` after a silence window rather than waiting for a retry that won't come. |
| **Re-validation**    | The `url` is re-checked against the SSRF guard at every delivery, not only at send time. If a once-public URL has flipped to a private/link-local address, the delivery is skipped.                                                             |

Answer `2xx`. Any other response (or a connection failure) is recorded as a
delivery failure on the task; the status transition itself still succeeds.

## 4. Verify the callback

Two signing schemes cover the callback; reject unsigned traffic at your
endpoint either way.

**Detached Ed25519 JWS — `X-Orbit-A2A-Push-Signature`.** When the Orbit
deployment has its agent signing key provisioned, this header carries a
compact JWS over the exact request body. Resolve the signing key from the
JWKS URL the AgentCard advertises at
`metadata.a2aBearerAuth.jwksUri` (also published on the Web Bot Auth
directory endpoint) and verify with the `kid` in the JWS header. This is the
preferred scheme: no shared secret crosses the integration.

**HMAC fallback — `X-A2A-Signature`.** When no signing key is provisioned,
Orbit falls back to the same shared-secret scheme used on inbound task
calls: `t=<unix_seconds>,v1=HMAC_SHA256(secret, "<t>.<raw_body>")`. Verify
with the A2A shared secret you already hold for that peer — see
[HMAC signing (task calls)](/agents/a2a-federation#hmac-signing-task-calls).

For mTLS between your gateway and Orbit's egress IPs, terminate it at your
edge and still apply one of the two payload signatures — transport
authentication and payload authentication answer different questions (who
dialed vs. who signed this body).

## 5. Tear down a subscription

Subscriptions die with the task — there is no separate unsubscribe call to
track.

* **Cancel the task** (`tasks/cancel`, or REST `PATCH …/a2a/tasks/{taskId}`
  with `{ "status": "cancelled" }`): the terminal transition is the last
  callback your URL receives.
* **Let the task finish:** `completed` / `failed` are terminal too; no
  further callbacks fire afterwards.
* **Rotate away:** if a registered callback URL must change mid-flight,
  finish/cancel the task and re-send it with the new target. The stored
  target on an existing task is never mutated.

## 6. Operator surface — seeing what's subscribed

The dashboard lists every push target along with the task it belongs to:

* **Agents → {agent} → A2A → Task history** — open a task row; the detail
  view shows the registered callback URL and whether the last delivery
  landed or was skipped.
* **API:** `GET /api/v1/agents/{agentId}/a2a/tasks/{taskId}` returns the
  task envelope; the subscribed target sits under `metadata.a2a_push`.

Wire `GET …/a2a/tasks` (authenticated list endpoint) into your own
operations dashboard if you need a fleet-level view of open subscriptions —
the same payload shape applies.

## Errors

| Symptom                                    | Cause                                                                                                                           |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `422` / `INVALID_PARAMS` at subscribe time | `url` is not public HTTPS, or the block is malformed.                                                                           |
| Callback never arrives                     | Delivery failure is only recorded on the Orbit side; check the task detail view, then fall back to `tasks/get`.                 |
| Callback arrives unsigned                  | No signing key provisioned on the Orbit side → HMAC fallback path. Verify the `X-A2A-Signature` header with your shared secret. |
| Stale callback URL after a task finished   | Expected — subscriptions are per task and terminal.                                                                             |

Work through [Troubleshooting: A2A federation](/troubleshooting/a2a-federation)
for the wider federation failure taxonomy.

## See also

* [A2A federation →](/agents/a2a-federation) — the endpoint reference this
  guide builds on.
* [Set up A2A federation between tenants →](/guides/a2a-federation-setup) —
  the peer-registration and handshake walkthrough.
* [A2A federation model →](/concepts/a2a-federation-model) — the concept
  page both guides assume.
* [Troubleshooting: A2A federation →](/troubleshooting/a2a-federation) —
  the failure taxonomy.
