4.6

Push notification configs

A TaskPushNotificationConfig tells an agent where to POST the same StreamResponse objects a stream carries, with credentials you chose, after your request has returned.

A test run can outlast any connection the planner wants to hold open. So the planner sends SendMessage with returnImmediately: true and a webhook, gets the task back in TASK_STATE_SUBMITTED, and closes the connection. From then on test-runner posts eight notifications to http://localhost:41250/a2a-events.

When you finish this section, you can register a webhook, authenticate what arrives, and build a receiver that stays correct when a notification comes twice.

The configuration object

TaskPushNotificationConfig: the object that tells an agent where to post updates for one task and how to authenticate them. Only url is REQUIRED. The optional token is unique to the task or the session, and the optional authentication holds a scheme such as Bearer and its credentials. The server assigns the config's id. The prose calls this object PushNotificationConfig, a type the proto does not define, so use the proto name (conflict D4).

a webhook registered inside SendMessagehttpFull log
POST /a2a/jsonrpc HTTP/1.1
…
  "method": "SendMessage",
  "params": {
    "message": {
      "messageId": "85750621-02fb-4d4f-b57f-bc5af71a1bfc",
…
    "configuration": {
      "returnImmediately": true,
      "taskPushNotificationConfig": {
        "url": "http://localhost:41250/a2a-events",
        "token": "planner-run-0006",
        "authentication": {
          "scheme": "Bearer",
          "credentials": "hook_secret_91d2"
        }
The headers and the message parts are cut. The configuration carries no task id, because the task does not exist yet.

Inline, as here, the config rides in configuration.taskPushNotificationConfig. That leaves no gap, because the kit and the reference SDK store it before the first event. For a task that already exists, CreateTaskPushNotificationConfig takes the config itself, with its taskId. The prose and Appendix A name a CreateTaskPushNotificationConfigRequest instead, which the proto does not define (conflict D5).

Agents SHOULD validate the url, as Security requirements explains.

What arrives at the webhook

Each notification is an HTTP POST whose body is one StreamResponse. The JSON is always the HTTP+JSON form, whatever binding the agent speaks. So test-runner posts a plain statusUpdate with no JSON-RPC envelope, as figure 4.8 shows.

Fig. 4.8one test run reported through a webhooksequence
A webhook for one test run Three lifelines: planner, test-runner and the planner webhook receiver on port 41250. The planner sends SendMessage with returnImmediately and a push configuration, and gets back the task in TASK_STATE_SUBMITTED at once. test-runner then posts eight notifications to the receiver: the working status with an Authorization Bearer header, five artifactUpdate chunks of test-log.txt, the summary.json artifact, and the completed status. The receiver answers each POST with 204 No Content. After the task has finished, the planner lists, reads, and deletes the push configuration, whose server-assigned id is 92c697ef. planner client agent test-runner localhost:41241 webhook receiver :41250/a2a-events 1 SendMessage returnImmediately, webhook config 2 task d7d3dceb SUBMITTED the call returns at once 3 POST statusUpdate WORKING Authorization: Bearer hook_secret_91d2 4 5 POSTs · artifactUpdate five chunks of test-log.txt 5 POST artifactUpdate summary.json, a data part 6 POST statusUpdate COMPLETED 1 failed, 11 passed 7 204 No Content the answer to every POST 8 ListTaskPushNotificationConfigs sent after the task finished 9 one config, id 92c697ef the id the server assigned 10 GetTaskPushNotificationConfig taskId and id 11 the same config 12 DeleteTaskPushNotificationConfig 13 result: {} no more POSTs for this config
With a webhook registered, test-runner posts eight StreamResponse bodies to the planner's receiver after the SendMessage call has already returned. Read top to bottom in the order the kit ran the steps. The capture file prints the eight deliveries last, but they arrived before the config calls.
the first notification, as the receiver saw ithttpFull log
POST /a2a-events HTTP/1.1
Content-Type: application/a2a+json
Authorization: Bearer hook_secret_91d2
X-A2A-Notification-Token: planner-run-0006

{
  "statusUpdate": {
    "taskId": "d7d3dceb-7333-4676-90ce-36542116a45c",
    "contextId": "e051d022-3c39-4f5c-bd9c-7a902ae474cd",
    "status": {
      "state": "TASK_STATE_WORKING",
…
      "timestamp": "2026-10-06T09:00:05.000Z"
The status message is cut. The kit's receiver records only these three headers.

The agent must put the credentials from PushNotificationConfig.authentication in the request headers. The specification gives the token no transport, and the kit sends it in X-A2A-Notification-Token, the reference SDK's header.

The specification's request format uses application/a2a+json, and the reference SDK posts application/json, as the kit README lists. The SDKs also differ on the token header and the timeout, and none retries:

SDKContent-TypeToken headerTimeoutRetries
a2a-python 1.2.2application/jsonX-A2A-Notification-Tokenset by the httpx client the server passes innone
a2a-goapplication/jsonA2A-Notification-Token30 snone
a2a-javaapplication/jsonX-A2A-Notification-Tokennone setnone
a2a-jsapplication/a2a+jsonX-A2A-Notification-Token, only when authentication is empty5 snone
a2a-dotnetno sender: every config operation returns -32003
a2a-rsapplication/jsonA2A-Notification-Token30 snone

Every sender in the table posts one StreamResponse per event. So accept both content types, read either token header, and put the secret you rely on in authentication. The specification does not say which events are posted, and the reference SDK also posts the first Task, so handle every StreamResponse member.

Delivery is attempted at least once

At least once means a notification can arrive twice, so the receiver has duties too. It must answer with an HTTP 2xx status to confirm receipt. It should process notifications idempotently, because duplicates can arrive. It must check that the task id matches a task it expects.

A status update is safe to apply twice, keyed by taskId and timestamp. An artifactUpdate with append: true is not, and the specification defines no delivery id. So treat each notification as a signal and read the task with GetTask, as the project's guide suggests.

Retries are optional: an agent is allowed to retry a failed delivery with exponential backoff. No SDK in the table retries, so a receiver that was down reads the task with GetTask when it comes back.

Managing configurations

Four operations manage the stored configs of a task: create, get, list, and delete. The capture lists, reads, and deletes its config after the run, as figure 4.8 shows. A config must stay until the task completes or until a client deletes it, and a delete must be idempotent.

Get and list return the stored credentials unchanged, in the kit and the reference SDK, so whoever can call them can read the webhook secret. All four operations need pushNotifications on the card, or they return PushNotificationNotSupportedError.

A2A protocol 1.0.1 · 3303592 · 2026-05-28 · Edition 2026.10