Skip to main content

Webhooks

Webhooks let Agenta notify another system when something changes. You can use them to update internal tools, sync deployed prompts into another service, or trigger downstream automation after a deployment.

When to use webhooks

Use a webhook when you want Agenta to push an event to your system.

Common use cases:

  • update a service after a prompt deploy
  • trigger a CI/CD job
  • notify an internal platform tool
  • sync prompt metadata into another store
info

If you want your application to fetch the latest prompt on demand, use Fetch Prompts via SDK/API.

Set up a webhook in the UI

  1. Open your project in Agenta.
  2. Go to Settings.
  3. Open the Automations section.

Project settings and automations entry point

  1. Click Create automation.
  2. Choose Webhook.
  3. Enter your HTTPS endpoint.
  4. Choose an authentication mode:
    • Signature if your receiver should verify an HMAC signature
    • Authorization header if you want Agenta to send a bearer token
  5. Select the events you want to subscribe to.
  6. Save the automation.
  7. Copy the secret when Agenta shows it. You will need it to verify future deliveries.

Create automation dialog for the webhook form

After you create the automation, Agenta sends an HTTP POST to your endpoint every time the selected event happens. For example, if you subscribe to deployment events, Agenta sends a delivery each time you deploy a new prompt revision.

What Agenta sends

Agenta sends a JSON body with three top-level objects:

  • event, which contains the event metadata and payload
  • subscription, which identifies the automation that fired
  • scope, which identifies the project

For environments.revisions.committed, the deployment details live in event.attributes.

references identifies the environment revision that was just committed.

state is the current value of data.references in that environment revision.

diff is the diff for that commit.

The overall request body looks like this:

{
"event": {
"event_id": "01961234-5678-7abc-9def-123456789abc",
"event_type": "environments.revisions.committed",
"timestamp": "2026-04-07T11:24:18.000Z",
"created_at": "2026-04-07T11:24:18.000Z",
"attributes": {
"...": "deployment payload"
}
},
"subscription": {
"id": "01961234-aaaa-7abc-9def-123456789abc"
},
"scope": {
"project_id": "01961234-bbbb-7abc-9def-123456789abc"
}
}

Here are two concrete event.attributes examples.

Example 1: first deployment of a new app

{
"user_id": "019315dc-a332-7ba5-a426-d079c43ab776",
"references": {
"environment": {
"id": "019c2b74-d84f-7cf2-aff0-e45e116e26cb"
},
"environment_revision": {
"id": "019cd9b8-e21c-7c73-82a2-099cb1352f19",
"slug": "prod-0001",
"version": "1"
},
"environment_variant": {
"id": "019c2b74-d85c-7803-8a55-f12f2fc8f461"
}
},
"state": {
"references": {
"customer-support-bot.revision": {
"application": {
"id": "019c2b74-d8b7-74e7-9f16-6a6a2c9cd111",
"slug": "customer-support-bot"
},
"application_variant": {
"id": "019c2b74-d8df-7f57-bb13-5e8c0c3f5222",
"slug": "production"
},
"application_revision": {
"id": "019cd9b8-e21c-7c73-82a2-099cb1352f19",
"slug": "prompt-v1",
"version": "1"
}
}
}
},
"diff": {
"created": {
"customer-support-bot.revision": {
"new": {
"application": {
"id": "019c2b74-d8b7-74e7-9f16-6a6a2c9cd111",
"slug": "customer-support-bot"
},
"application_variant": {
"id": "019c2b74-d8df-7f57-bb13-5e8c0c3f5222",
"slug": "production"
},
"application_revision": {
"id": "019cd9b8-e21c-7c73-82a2-099cb1352f19",
"slug": "prompt-v1",
"version": "1"
}
}
}
},
"updated": {},
"deleted": {}
}
}

Example 2: deploying a new revision for an app that is already in the environment

{
"user_id": "019315dc-a332-7ba5-a426-d079c43ab776",
"references": {
"environment": {
"id": "019c2b74-d84f-7cf2-aff0-e45e116e26cb"
},
"environment_revision": {
"id": "019cd9c9-a742-78e0-9c0d-a08da7df88a1",
"slug": "prod-0008",
"version": "8"
},
"environment_variant": {
"id": "019c2b74-d85c-7803-8a55-f12f2fc8f461"
}
},
"state": {
"references": {
"customer-support-bot.revision": {
"application": {
"id": "019c2b74-d8b7-74e7-9f16-6a6a2c9cd111",
"slug": "customer-support-bot"
},
"application_variant": {
"id": "019c2b74-d8df-7f57-bb13-5e8c0c3f5222",
"slug": "production"
},
"application_revision": {
"id": "019cd9c9-a742-78e0-9c0d-a08da7df88a1",
"slug": "prompt-v8",
"version": "8"
}
}
}
},
"diff": {
"created": {},
"updated": {
"customer-support-bot.revision": {
"old": {
"application": {
"id": "019c2b74-d8b7-74e7-9f16-6a6a2c9cd111",
"slug": "customer-support-bot"
},
"application_variant": {
"id": "019c2b74-d8df-7f57-bb13-5e8c0c3f5222",
"slug": "production"
},
"application_revision": {
"id": "019cd9a1-3fd6-7144-9c0d-fcbf0a6fd777",
"slug": "prompt-v7",
"version": "7"
}
},
"new": {
"application": {
"id": "019c2b74-d8b7-74e7-9f16-6a6a2c9cd111",
"slug": "customer-support-bot"
},
"application_variant": {
"id": "019c2b74-d8df-7f57-bb13-5e8c0c3f5222",
"slug": "production"
},
"application_revision": {
"id": "019cd9c9-a742-78e0-9c0d-a08da7df88a1",
"slug": "prompt-v8",
"version": "8"
}
}
}
},
"deleted": {}
}
}

Agenta also sends system headers like these:

Content-Type: application/json
User-Agent: Agenta-Webhook/1.0
X-Agenta-Event-Type: environments.revisions.committed
X-Agenta-Delivery-Id: <delivery_id>
X-Agenta-Event-Id: <event_id>
Idempotency-Key: <delivery_id>

Available event types

Agenta emits events for both read access and Git-style revision lifecycle changes. You can subscribe to any subset of the events below.

Revision lifecycle events

Each major entity that has revision history exposes the same five action events:

ActionEmitted when
retrievedA revision is retrieved by reference (slug or id)
fetchedA revision is fetched by id (GET /<domain>/revisions/{id})
queriedA list of revisions is returned from POST /<domain>/revisions/query
loggedA revision history log is returned from POST /<domain>/revisions/log
committedA new revision is committed (direct commit, simple-service create/edit, deploy, fork, etc.)

The supported domains and event names are:

  • applications.revisions.{retrieved,fetched,queried,logged,committed}
  • queries.revisions.{retrieved,fetched,queried,logged,committed}
  • testsets.revisions.{retrieved,fetched,queried,logged,committed}
  • evaluators.revisions.{retrieved,fetched,queried,logged,committed}
  • environments.revisions.{retrieved,fetched,queried,logged,committed}

Revision read events (retrieved, fetched, queried, and logged) carry references (the artifact, variant, and revision identifiers — partial when the response does not expose all fields), count, and user_id. Commit events carry references, user_id, and optional message, but do not include count. The environments.revisions.committed event additionally carries state and diff describing the committed configuration, as shown in the examples above.

For list events (queried, logged), references is an array capped at 1000 entries while count reflects the uncapped total.

Read events for traces and testcases

  • traces.fetched — fired by GET /traces/{trace_id} (carries trace_id) and GET /traces/ (carries capped trace_ids)
  • traces.queried — fired by POST /traces/query (carries capped trace_ids)
  • testcases.fetched — fired by GET /testcases/{testcase_id} (carries testcase_id) and GET /testcases/ (carries capped testcase_ids)
  • testcases.queried — fired by POST /testcases/query (carries capped testcase_ids)

Read events always carry user_id and count. They are only emitted when count > 0.

System events

  • webhooks.subscriptions.tested — fired when you click Send test on a webhook subscription

Authentication modes

Using signature mode

Use signature mode when the receiver should verify that the request really came from Agenta and that the payload was not changed in transit.

Agenta signs each delivery and sends:

X-Agenta-Signature: t=<unix_ts>,v1=<hex_hmac>

Agenta computes the signature as:

HMAC_SHA256(secret, "{timestamp}.{raw_body}")

The important detail is raw_body. Agenta signs the exact JSON string it sends over HTTP.

To verify the signature:

  1. Read the raw request body before modifying it.
  2. Parse t= and v1= from X-Agenta-Signature.
  3. Compute HMAC_SHA256(secret, "{timestamp}.{raw_body}").
  4. Compare your result with v1= using constant time comparison.
  5. Reject old timestamps to reduce replay risk.
import hashlib
import hmac
import os

from fastapi import FastAPI, HTTPException, Request

app = FastAPI()
WEBHOOK_SECRET = os.environ["AGENTA_WEBHOOK_SECRET"]


@app.post("/webhook")
async def webhook(request: Request):
signature_header = request.headers.get("x-agenta-signature")
if not signature_header:
raise HTTPException(status_code=401, detail="Missing signature")

parts = dict(piece.split("=", 1) for piece in signature_header.split(","))
timestamp = parts.get("t")
received = parts.get("v1")
if not timestamp or not received:
raise HTTPException(status_code=401, detail="Malformed signature")

raw_body = (await request.body()).decode("utf-8")
expected = hmac.new(
WEBHOOK_SECRET.encode("utf-8"),
f"{timestamp}.{raw_body}".encode("utf-8"),
hashlib.sha256,
).hexdigest()

if not hmac.compare_digest(received, expected):
raise HTTPException(status_code=401, detail="Invalid signature")

payload = await request.json()
return {"received": True, "payload": payload}

Here is a working example hosted on Val Town.

Authorization header mode

In authorization mode, Agenta sends your stored secret as the Authorization header.

Authorization: Bearer <token>

Use this mode when your target system already expects a bearer token and does not need HMAC verification.

Troubleshooting

Signature mismatch

Check these first:

  • use the exact secret shown when you created the automation
  • verify the exact raw body, not a re-serialized JSON object
  • make sure you parse the signature header as t=...,v1=...
  • confirm you use HMAC-SHA256

HTTPS is required

Agenta expects webhook URLs to use HTTPS. If you test locally, use a tunnel such as Cloudflare Tunnel or ngrok.

Retries

If your endpoint returns a non 2xx response or times out, Agenta retries the delivery.