Skip to main content
Anchor delivers webhook events to your endpoint the moment something interesting happens - a task completes, a login fails, the agent needs human input. Each delivery is signed with HMAC-SHA256 so you can verify it came from Anchor and not someone spoofing your endpoint.

Quick Start

1. Create a webhook

From the dashboard, open Webhooks under the Account section and click Add webhook:
  • Paste your HTTPS endpoint (https://your-app.example.com/anchor/webhooks).
  • Pick the events you want to subscribe to. See Events for the full catalog.
  • Click Create webhook.
Anchor shows the signing secret exactly once. Copy it now - in order to verify incoming requests, we do NOT show it again. If you lose it, click Rotate signing secret to issue a new one (the old secret remains valid for 24 hours). You can also create webhooks via the API:
curl -X POST "https://api.anchorbrowser.io/v1/webhooks" \
  -H "anchor-api-key: $ANCHOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/anchor/webhooks",
    "description": "Production task notifications",
    "subscribed_events": ["task.completed", "task.failed", "task.healed"]
  }'
The response includes the secret field - store it, it never appears in any subsequent API response.

2. Receive events

Each event arrives as a POST with a JSON body and a few Anchor-* headers:
POST /anchor/webhooks
Host: your-app.example.com
User-Agent: Anchor-Webhooks/1.0
Content-Type: application/json
Anchor-Event: task.completed
Anchor-Delivery-Id: <uuid>
Anchor-Timestamp: 1716544084
Anchor-Signature: t=1716544084,v1=<hex hmac sha256>

{
  "id": "evt_01HXJ4...",
  "type": "task.completed",
  "created": "2026-05-24T12:18:05.123Z",
  "project_id": "5d2c31f6-ab7e-481a-b6fd-8b4a96a4e197",
  "text": "✅ Task `tsk_4f8w9n2b` completed in 4.2s",
  "data": { "...": "see Events for per-type payloads" }
}
Always respond with a 2xx status code within 10 seconds - that’s how Anchor knows the event was delivered. Anything else triggers a retry (see Retries).

3. Verify the signature

Compute an HMAC-SHA256 of v0:{Anchor-Timestamp}:{raw_body_bytes} using your signing secret and compare it to the v1= value in the Anchor-Signature header in constant time. Reject anything you can’t verify. Full code examples (Node.js, Python, Go) are on the Signature verification page.

Slack integration

Every event includes a top-level text field with a one-line human-readable summary. That’s the same shape Slack incoming webhooks expect, so you can register a Slack URL as a webhook receiver with no relay:
  1. In Slack, create an Incoming Webhook for the channel you want events posted to. Slack gives you a URL like https://hooks.slack.com/services/T.../B.../....
  2. In Anchor, Webhooks → Add webhook, paste the Slack URL, pick the events.
  3. Done. Each delivery posts a message in the channel — for example:
    ✅ Task tsk_4f8w9n2b completed in 4.2s
For richer formatting (blocks, mentions, attachments) write a small relay that reads data and posts the message you want; the structured fields stay machine-readable while text covers the simple case.

Testing your endpoint

The dashboard’s Send test event button fires a synthetic event of the type you choose through the real signing + retry path. Use it during development:
  1. Pick the event type from the dropdown next to the payload preview.
  2. Click Send test event.
  3. Inspect the request at your endpoint.
The test delivery counts toward your delivery log so you can verify retry behaviour by intentionally returning 503 from your endpoint. You can also trigger via the API:
curl -X POST "https://api.anchorbrowser.io/v1/webhooks/<webhook_id>/test" \
  -H "anchor-api-key: $ANCHOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "event_type": "task.completed" }'

Retries and delivery guarantees

Anchor retries failed deliveries with exponential backoff:
AttemptTime since first attemptSchedule (after the previous)
1 (initial)0 simmediate
25 s+5 s
315 s+10 s
430 s+15 s
550 s+20 s
680 s+30 s
A delivery is retried when:
  • The HTTP request errors (DNS failure, timeout, TLS handshake failure).
  • Your endpoint returns 5xx, 408, or 429.
A delivery is not retried when your endpoint returns any other 4xx. Use that intentionally for events you’ve already processed (409 Conflict after de-dupe is a common pattern). After 6 attempts (~80 s elapsed since the first attempt), the delivery is marked dead and won’t be retried automatically. You can still see it in Webhooks → Events in the dashboard for 7 days. At-least-once delivery. Because we retry on transient errors, your endpoint may receive the same event more than once. Always de-dupe by id (the envelope’s top-level id, not data.execution_id).

Idempotency and ordering

  • Each delivery has a unique top-level id (evt_...). The same logical event will always carry the same id, so storing (id, status) in your database and checking before processing is enough to make your handler idempotent.
  • Anchor does not guarantee delivery order. A retry may arrive after a later event for the same task. Use the created timestamp to order events on your side if you care about sequencing.
  • An event delivered to multiple webhook endpoints carries the same id to every endpoint, so you can correlate deliveries across consumers.

Inspecting deliveries

Every delivery - succeeded, failed, or dead - is visible in the dashboard’s Webhooks → Events view for 7 days. The same data is available via the API:
curl "https://api.anchorbrowser.io/v1/webhooks/<webhook_id>/events?status=failed&limit=25" \
  -H "anchor-api-key: $ANCHOR_API_KEY"
Each row shows event_type, status, attempt, response_status, error_message, scheduled_at, and completed_at. Use this to debug failing endpoints or to confirm a missed event made it through.

Sensitive data and what we never send

Anchor sanitizes every webhook payload before it leaves our infrastructure:
  1. Whitelist via schema. Only fields explicitly declared in each event’s schema are sent. Anything else is stripped.
  2. Schema-driven secret redaction. Task inputs marked secret in your task schema are masked as ***** before they appear in output.value.
  3. Free-text scrubbing. Error messages and self-heal explanations are scanned for credential patterns and masked.
What is never in any payload:
  • Identity credential values (passwords, API tokens).
  • Raw inputs from task executions.

Best practices

Even if you trust your network perimeter - Anchor’s signature is a proof that the request really came from us.
Return 2xx within 10 seconds. If processing takes longer (e.g. you write to a slow downstream system), enqueue the event in your own queue and process it asynchronously.
Anchor delivers at-least-once. Persist (id, status) in your database and short-circuit duplicates with a 200 OK.
Click Rotate signing secret to get a new one. The old secret remains valid for 24 hours so you can deploy at your own pace. Update both, then drop the old one once your deploys are out.
Filter by status=failed or status=dead in the dashboard or API. Anything that lingers there is either an endpoint bug, a network blip, or a real automation issue worth digging into.

API reference

MethodPathPurpose
POST/v1/webhooksCreate a webhook (returns the signing secret once).
GET/v1/webhooksList webhooks for the project.
GET/v1/webhooks/{id}Fetch a single webhook config.
PATCH/v1/webhooks/{id}Update url, description, subscribed events, or enabled.
POST/v1/webhooks/{id}/rotate-secretRotate the signing secret. Old secret valid for 24 h.
DELETE/v1/webhooks/{id}Soft-delete a webhook.
POST/v1/webhooks/{id}/testFire a synthetic event of a given type.
GET/v1/webhooks/{id}/eventsPaginated delivery history (last 7 days).
All routes are authenticated with the anchor-api-key header (or the dashboard session cookie) and scoped to the current project.

Limits

  • Up to 5 webhook endpoints per project.
  • HTTPS only. The following hosts are rejected at create/update time:
    • Loopback / private / link-local IPs (127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, 169.254.0.0/16, IPv6 ULA fc00::/7 and link-local fe80::/10).
    • Cloud metadata services (169.254.169.254, metadata.google.internal, metadata.azure.com).
    • Internal-only TLDs (*.local, *.internal).
    • Cloud control-plane endpoints — AWS STS (sts.*.amazonaws.com, including FIPS, GovCloud, and China regions), AWS IAM, EKS auth.
    • URLs with embedded basic-auth credentials.
  • Maximum URL length: 2048 characters.
  • Maximum payload size: 64 KB. Larger output.value is replaced with "__truncated__"; fetch the full result via the REST API.
  • HTTP timeout: 10s per attempt.
  • Maximum 6 delivery attempts (1 initial + 5 retries) over ~80 seconds.
  • Delivery history retention: 7 days.