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

# Reading your agent's webhook results

> How an agent's owner reads whether the callbacks the agent sends Apostra were accepted, what each refusal reason means, and how to fix it.

When your sales agent or inventory source finishes an async task, such as `create_media_buy`,
`update_media_buy`, `sync_creatives` or `sync_accounts`, it sends the result back to Apostra as a
callback. We refuse a callback that fails a check, and the response we send is deliberately generic
(for example `401` with no cause), so a sender cannot use it to probe the verifier. You do not have
to guess the cause. The owner of the agent can read what we recorded, including the specific reason.

## Read the results

Call `get` with `kind: "agent"`, the agent's `id`, and `include: ["webhookDeliveries"]`. The
object carries `webhookDeliveries`:

```json theme={null}
{
  "windowDays": 30,
  "hasMore": false,
  "sourcesTruncated": false,
  "items": [
    {
      "at": "2026-10-20T12:00:00.000Z",
      "sourceId": "src_1",
      "sourceName": "Homepage",
      "taskKind": "create_media_buy",
      "outcome": "refused",
      "reasonCode": "webhook_signature_invalid",
      "reason": "The signature did not verify: it was missing, malformed, or made with a key we do not have for this agent.",
      "signatureScheme": "rfc9421",
      "signatureReason": "signature_mismatch"
    }
  ]
}
```

| Field | Meaning |
| - | - |
| `windowDays` | How far back the read looks. It is 30 days. |
| `items` | The newest outcomes first. A read returns at most 50. |
| `hasMore` | `true` when older outcomes inside the window were not returned. |
| `sourcesTruncated` | `true` when the agent has more than 500 Inventory Sources in your organization, so the outcomes of the sources beyond the first 500 (by id) were not read. It is independent of `hasMore`. |
| `at` | When we received the callback. |
| `sourceId`, `sourceName` | The Inventory Source the callback was for. |
| `taskKind` | The operation: `create_media_buy`, `update_media_buy`, `sync_creatives` or `sync_accounts`. An accepted `sync_accounts` callback is not recorded, so that operation appears only on refusals. |
| `outcome` | `accepted`, or `refused`. A refused callback was not applied. |
| `reasonCode`, `reason` | On a refusal, the code from the table below and what it means. |
| `signatureScheme`, `signatureReason` | On a refusal from a signature check, the scheme we checked (`hmac` or `rfc9421`) and the check's own reason code. |
| `repeats` | How many identical refusals followed the previous entry without being stored. |

Only an administrator of the account that owns the agent can read this: it admits the same accounts
and people as reading the agent itself. An agent's results never appear in another organization's reads. The
read covers the Inventory Sources that belong to your own organization's accounts; an agent that
serves publishers in other organizations shows only the callbacks for your own sources.

### What the read does not show

* **At most 500 sources.** A read searches an agent's first 500 Inventory Sources by id. When the agent has more, `sourcesTruncated` is `true` and the results do not include the rest, whatever `hasMore` says.
* **One entry per minute for a repeated refusal.** We store at most one refusal a minute for the
  same source, operation and reason, and `repeats` counts the ones that followed. A sender that
  retries a refused callback in a tight loop reads as one entry with a count.
* **Not every callback route.** Results cover the callbacks your agent sends to Apostra for your
  Inventory Sources: refusals, accepted media-buy callbacks, and creative-sync callbacks. A callback
  your agent sends to the callback URL we gave it as a sales agent, and an accepted
  `sync_accounts` callback, are counted but not recorded here.
* **Callbacks we could not tie to a source.** A callback for a source we cannot identify has
  nothing to attach to, so it does not appear.

## Refusal reasons

Each refusal carries one of these codes. The first column is the `reasonCode`.

| Code | What happened | What to do |
| - | - | - |
| `webhook_signature_invalid` | The signature was missing, malformed, or made with a key we do not have for your agent. | Check `signatureScheme` and `signatureReason`, then follow [Signing the webhooks you send us](/v3/seller-webhook-signing): sign the raw body with the key for the profile you chose, cover the required components, and sign the callback URL exactly as we sent it. |
| `webhook_verification_unavailable` | We could not finish checking the signature, for example because we could not fetch the keys your agent publishes. The signature was not judged. | Check that your key directory or `brand.json` is reachable from the public internet, then resend. If it repeats and your keys are reachable, contact us. |
| `webhook_mode_mismatch` | The callback was signed with a different scheme than the one it was registered for. | Sign with the scheme you declared for the operation: the one in the `push_notification_config` we sent. |
| `webhook_operation_not_registered` | We have no live callback registration for this operation, or it was registered for a different signer. | Send a callback only for an operation we sent you, to the URL we gave you, signed by the agent we registered it for. |
| `webhook_agent_mismatch` | The callback went to a URL for a different agent than the one it was registered for. | Use the callback URL exactly as we sent it, for each operation. Do not reuse one agent's URL for another. |
| `webhook_url_invalid` | The URL has a selector we never issue. | Do not build callback URLs yourself. Use the one in `push_notification_config.url`. |
| `webhook_payload_invalid` | The body was not a valid AdCP webhook for this operation. | Check the body against the AdCP schema for the task. |
| `webhook_payload_not_handled` | The body was a valid message that we do not handle on this callback URL. | Send task results for operations we sent you. |
| `webhook_verdict_conflict` | The final status contradicts the final status we already recorded for this operation. | A settled operation does not change. To correct it, start a new operation. |
| `task_id_missing` | The callback did not say which task it reports on. | Include the `task_id` we gave your agent. |
| `task_not_pending` | No pending operation carries the callback's task. It arrived before we recorded the task, after the operation settled, or for a task we did not send. | A callback for a settled operation is harmless. If it was for a live operation, send it again once the operation has settled on your side. If you never sent that task, check the `task_id`. |
| `webhook_body_unavailable` | We could not read the raw body, so we could not check the signature. This is on our side. | Resend. If it repeats, contact us. |
| `webhook_processing_unavailable` | We could not finish applying the callback and asked you to send it again. This is on our side. | Redeliver. We answered with a retryable status, so a standard retry does this. |

A callback that fails with `task_id_missing` or `task_not_pending` is acknowledged with a success
status, because there is nothing for the sender to retry. It still reads as `refused` here, because
we did not apply it.

## Accepted callbacks

An `accepted` entry means we applied the callback. It says nothing about the status the callback
reported: a seller's task can fail and its callback still be accepted.

## Webhook signing readiness

The results also feed the `webhook_signing` check in
[Getting ready for AdCP 4.0](/v3/adcp-4-readiness#webhook-signing-webhook_signing). An agent that
declares RFC 9421 webhook signing fails that check when, in the last 7 days, we refused at least 3
of its callbacks because their signatures did not verify (`webhook_signature_invalid` with
`signatureScheme` `rfc9421`, repeats included). It clears when fewer than 3 remain in that window.

## Related

* [Signing the webhooks you send us](/v3/seller-webhook-signing) covers the three signing profiles.
* [Getting ready for AdCP 4.0](/v3/adcp-4-readiness) covers when HMAC webhook signing stops being accepted.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.