In asynchronous mode, the harness saves the intercepted tool call and suspends execution whilst waiting for approval, so the agent process does not need to keep running. This lets an approval outlive the agent process or its network connection, which suits approvals that may take hours or days.
When a decision is made, the provider sends a signed webhook to a receiver. The receiver is an HTTPS endpoint, part of the harness or a separate service, that stays reachable whilst the agent is suspended. It verifies the notification, then wakes the suspended execution, which fetches the decision and continues.
A provider supporting asynchronous mode must support webhook delivery.
Flow
Loading diagram…
Diagram source
sequenceDiagram
participant Agent
participant Adapter as Adapter / harness
participant Receiver
participant Provider
participant Tool
Note over Receiver,Provider: Receiver verified during instance provisioning
Agent->>Adapter: Call tool
Adapter->>Provider: POST /v1/requests
Provider-->>Adapter: Pending request
Adapter->>Adapter: Save pending execution and suspend
Provider->>Provider: Save decision and notification
Provider->>Receiver: Signed request.resolved notification
Receiver->>Receiver: Verify signature
Receiver-->>Provider: 204 No Content
Receiver->>Adapter: Resume pending execution
Adapter->>Provider: GET /v1/requests/{id}
Provider-->>Adapter: Request with recorded decision
alt Approved and approval still valid
Adapter->>Provider: POST /v1/requests/{id}/consume
Provider-->>Adapter: Consumed request
Adapter->>Tool: Execute approved call
Tool-->>Adapter: Result
Adapter-->>Agent: Result
else Call not permitted
Adapter-->>Agent: Call did not run
endIf the initial submission already returns a terminal request, the adapter applies it immediately and does not suspend.
Delivery Configuration
The provisioner configures delivery when creating or updating an instance by supplying a delivery object:
type: alwayswebhookin version 1.url: the receiver's HTTPS address.registration_id: a UUID the provisioner generates and stores with the secret on the receiver beforehand, so the receiver can find the secret before it knows the instance ID. Reuse it when retrying the same submission, and generate a new one for each replacement.signing_secret: 32 cryptographically random bytes, encoded as padded base64 with awhsec_prefix, separate for each instance. The provider never returns it, and it must be kept from the agent.
An instance without delivery configuration can only use synchronous mode. Approval requests cannot override the instance's configuration.
Registration Example
The provisioner stores a registration ID and secret on the receiver at https://harness.example.com/aap/events, then submits:
POST /v1/instances HTTP/1.1
Authorization: Bearer <provisioner_token>
Content-Type: application/json
Idempotency-Key: 673214be-26e4-44b2-8ef6-321df40fd265
{
"instance_name": "codex-chris-laptop",
"delivery": {
"type": "webhook",
"url": "https://harness.example.com/aap/events",
"registration_id": "8246931c-4ce0-4c15-a94a-e9c078ad06e2",
"signing_secret": "whsec_AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="
}
}The example secret is illustrative.
The provider verifies the receiver, then returns 201 Created with the instance and its credential.
Receiver Verification
Before creating an instance or replacing its delivery configuration, the provider sends a signed WebhookVerification to the proposed URL:
{
"id": "c9bb5281-f9a5-4660-b27e-0b1cbbad81b6",
"type": "webhook.verification",
"registration_id": "8246931c-4ce0-4c15-a94a-e9c078ad06e2",
"instance_id": "b29a43a7-1949-4b9f-9d61-b7ae66f85d31",
"created_at": "2026-09-16T11:59:00Z",
"challenge": "2edc34d7-cc57-44b1-8705-2fa608d3224a"
}The challenge is fresh and unpredictable for each attempt, and verification is bound to the exact URL and proposed configuration.
The receiver looks up the secret by registration_id, verifies the signature and checks that it expects this registration.
It must not accept every registration from a recognised provider.
It then stores the instance_id, which later notifications use to identify the secret, and returns 200 OK with:
{
"challenge": "2edc34d7-cc57-44b1-8705-2fa608d3224a"
}The challenge must match exactly within ten seconds; any other response fails.
The provider makes one attempt and does not retry.
On failure, it returns 422 with code webhook_verification_failed: no instance is created, or an update leaves the existing configuration and name unchanged.
The provisioner may retry with the same idempotency key and input, subject to the provider's registration limits.
Updating and Removing Delivery
The provisioner replaces delivery configuration through PATCH /v1/instances/{id} with a complete delivery object and a new registration ID, including when only rotating the secret.
The old configuration stays active until the replacement is verified.
Setting delivery to null removes the receiver and stops delivery.
After a replacement, undelivered notifications go to the new receiver with unchanged IDs, bodies and retry deadlines. Completed deliveries are not replayed. An attempt already in flight may still reach the old receiver, so the provisioner must coordinate both receivers and their saved execution state during a move.
Deleting the instance stops delivery and cancels its pending requests without notifications.
Notification
When a request becomes approved, denied, expired or cancelled and delivery is configured, the provider records one RequestResolvedEvent:
{
"id": "f4b96ac4-404d-4f6a-9a03-a0565edc4922",
"type": "request.resolved",
"instance_id": "b29a43a7-1949-4b9f-9d61-b7ae66f85d31",
"request_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"created_at": "2026-09-16T12:02:00Z"
}created_at equals the decision's decided_at.
The event carries no outcome, tool arguments, credentials or permission to execute.
The provider must record the event durably together with the decision, including decisions made at creation or by cancellation. Reading or replaying a request creates no event, and adding a receiver creates none for requests that were already terminal. The event ID and body never change. Notifications for different requests may arrive in any order.
The OpenAPI schema defines the event, verification objects and receiver operations.
Delivery Example
The adapter has submitted the request from the synchronous example and suspended.
It is approved at 12:02:00Z, and the provider notifies the registered receiver at 12:02:01Z:
POST /aap/events HTTP/1.1
Host: harness.example.com
Content-Type: application/json
webhook-id: f4b96ac4-404d-4f6a-9a03-a0565edc4922
webhook-timestamp: 1789560121
webhook-signature: v1,zgT2qt+QiEEonu9tmZqEuKzoa83SGE9ZcCgUHzqSPZ8=
{
"id": "f4b96ac4-404d-4f6a-9a03-a0565edc4922",
"type": "request.resolved",
"instance_id": "b29a43a7-1949-4b9f-9d61-b7ae66f85d31",
"request_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
"created_at": "2026-09-16T12:02:00Z"
}The receiver verifies the signature and acknowledges the notification:
HTTP/1.1 204 No ContentThe adapter then retrieves the request.
Its decision expires at 12:07:00Z, so the adapter must consume the approval before then.
Signing
Verification messages and notifications use HTTPS POST with Content-Type: application/json and Standard Webhooks' HMAC-SHA256 scheme.
The required headers are:
| Header | Value |
|---|---|
webhook-id | The message's id. |
webhook-timestamp | The delivery attempt time in Unix seconds. |
webhook-signature | v1, followed by the base64 HMAC digest. |
The signing key is the configured secret without whsec_, base64-decoded.
Sign webhook-id, ., webhook-timestamp, . and the exact transmitted body bytes, in that order, using UTF-8 for the header values and separators.
Do not parse and reserialize JSON before verification.
The header may contain space-separated signatures; a valid v1 signature is required.
Each retry is signed again with a fresh timestamp.
The receiver must compare signatures in constant time, reject timestamps more than five minutes from its clock, check that the body ID matches webhook-id and check that notifications match the instance bound during verification.
Providers and receivers must keep their clocks synchronized.
The provider never sends instance credentials or provisioner tokens to the receiver.
Acknowledgements and Retries
The receiver verifies a notification before returning any 2xx, such as 204 No Content.
Acknowledgement confirms receipt only, and the provider ignores the response body.
The receiver may acknowledge before it resumes execution, and should, because each delivery attempt has a ten second limit.
Duplicates also receive 2xx.
The provider delivers promptly and retries failures with exponential backoff and jitter until acknowledged, for up to seven days after decision.decided_at.
Each event has at most one attempt outstanding, and each attempt times out within ten seconds and ends by the retry deadline.
Connection failures, timeouts and non-2xx responses are failures.
The provider must not follow redirects, and must respect Retry-After on 429 and 503 without extending the retry window.
Delivery continues after the approval expires, and extends how long the provider retains the request.
Resuming Execution
Before suspending, the harness saves the approval request ID, the original idempotency key and the exact pending tool call. A notification can arrive before suspension finishes; the resumed execution then finds the request already decided.
On resuming, the adapter must retrieve the request rather than act on the event, check decision.expires_at and consume the approval before the call starts.
A notification only wakes execution. Receivers do not need to remember event IDs: a repeated or forged event at most wakes an execution again, and consumption ensures the call runs at most once. Execution that was abandoned simply finds nothing to resume.
Recovery
If an event is missed, or the receiver acknowledged it and then failed before resuming, the execution stays suspended. The harness recovers by periodically retrieving its suspended requests by their saved IDs and resuming those that are terminal. Decisions remain readable for the retention period whether or not delivery succeeded, but only with a valid credential.
If execution is abandoned, the adapter must attempt to cancel the pending request. Suspending execution is not abandonment and must not cancel it.