A webhook that may fire twice or not at all

L3SeniorTrade-off10 minSystem designDesign review

RolesAgent Systems EngineeringCustomer AI Deployment

The question

A test run outlasts any connection your planner can hold open, so you want a webhook. Compare a webhook with polling and with streaming for this case. Then list the receiver's duties, given that the agent promises only to try each delivery at least once, and that the config operations return the stored webhook credentials to anyone who can call them.

Answer it first, aloud or on paper. Then open the answer.

The answer

Show short answer

Register the config inline in SendMessage with returnImmediately: true, so that no event escapes before the config exists, and close the connection. Delivery is at least once, so a notification can arrive twice or, after an outage, not at all: the receiver answers 2xx, checks the task id, processes idempotently, and reads the task with GetTask before it acts. Put the secret your receiver checks in authentication, because token has no standard header, and restrict who can call the four config operations, because a read returns the credentials in clear.

Show full answer

Three ways to wait

MechanismHolds a connectionLearns of a changeCost when the client restarts
polling with GetTasknoon the next pollnone, the task is stored
SendStreamingMessage or SubscribeToTaskyesat oncemissed frames; subscribe again for a snapshot
webhooknoat once, when delivery succeedsmissed deliveries; read the task

A webhook suits work that outlasts the caller. It moves the connection problem to the receiver and adds a second trust boundary.

What arrives

Each notification is one HTTP POST whose body is one StreamResponse in the HTTP+JSON form, with no JSON-RPC envelope. In the recorded run the first body was a statusUpdate with TASK_STATE_WORKING, under Content-Type: application/a2a+json, Authorization: Bearer <secret> and X-A2A-Notification-Token. The SDKs differ on the content type, on the token header, and on timeouts of 5 s or 30 s, and none of them retries. So accept both content types, read either token header, and handle every StreamResponse member, because the reference SDK also posts the first Task.

The receiver's duties

  1. Answer with a 2xx status to confirm receipt.
  2. Check that the task id matches a task you expect.
  3. Process idempotently. A status update is safe to apply twice, keyed by task id and timestamp. An artifactUpdate with append: true is not, and no delivery id exists.
  4. Treat each notification as a signal and read the task with GetTask before you act.
  5. Check the Authorization value against the credential you put in authentication.

The agent's duties, and where they fail

The agent must put the configured credentials in the request headers and should validate the URL against server-side request forgery, rejecting private ranges such as 10.0.0.0/8 and 192.168.0.0/16, loopback and link-local addresses. The reference SDK's validator does that only when the host turns it on. A config must stay until the task completes or a client deletes it, and a delete must be idempotent.

The trade

Inline registration costs one field and closes the gap between the first event and the config. The secret in authentication costs a per-run value and buys a check the receiver can make. The read-back of credentials in clear is the price of the four config operations, so scope them to the task's owner.

Follow-ups

  1. L3SeniorAn artifactUpdate with append: true arrives twice. Why can you not apply it twice?

    Show expected answer

    it duplicates parts, and there is no delivery id to deduplicate on, so read the task instead.

  2. L3SeniorThe receiver was down for a minute. What did it miss?

    Show expected answer

    possibly everything, because no SDK retries; read the task when the receiver returns.

  3. L4StaffWhy must the agent reject a webhook URL inside a private range?

    Show expected answer

    the agent calls that URL for every later event of the task, so an outside caller could aim the agent's requests at an internal service.

  4. L4StaffWho may call ListTaskPushNotificationConfigs, and why?

    Show expected answer

    only the task's owner, because the reply returns credentials and token unchanged.

Wrong answers

Show the wrong answers that interviewers hear
  • “Register the webhook after the task starts with the create operation.”

    Events before the config exists are lost. Inline registration leaves no gap.

  • “Authenticate on the token header.”

    The specification gives token no transport, and the SDKs use two header names. Put the secret in authentication and check Authorization.

  • “Apply each notification as it arrives.”

    Duplicates and lost deliveries both happen. The stored task is the source of truth.

Taught in

The push notification section records the eight deliveries and the SDK table, and the security requirements section lists the webhook rules.