Skip to content
Docs
Build Webhooks

Webhooks

Trigger workflows from external events sent by Standard Webhooks, Sentry, Stripe, GitHub, or Slack, with signature verification and idempotent delivery.

A webhook lets an external system start one of your workflows by POSTing an event to AGNT5. The gateway verifies the sender’s signature, turns the delivery into a durable event, and starts every workflow subscribed to it. You write a workflow and declare which event it listens for. AGNT5 handles receipt, verification, deduplication, and dispatch.

This page covers the mechanism end to end using Standard Webhooks, a publisher you control. To connect a specific third-party service, see Integrations → Event sources, which layers provider-specific setup on top of everything here.


How it works

  1. You create a webhook integration in Studio and receive a URL and a signing secret.
  2. The external system POSTs events to /v1/webhooks/{source}/{integration_id} (Studio shows the full URL).
  3. The gateway verifies the HMAC signature against your secret and rejects anything that fails with 401 before any workflow runs.
  4. It derives an event name of the form {source}.{event} and starts every workflow whose trigger matches.
  5. It returns 200 once the event is durably persisted to the log, which happens before your workflow finishes running.

Declare a trigger

Subscribe a workflow to a webhook event with the webhook() trigger:

from agnt5 import workflow, webhook

@workflow(
    name="triage_issue",
    triggers=[webhook("sentry", event="issue.created")],
)
async def triage_issue(ctx, event: dict) -> dict:
    payload = json.loads(event["body"])  # raw request body, as a string
    issue = payload["data"]["issue"]
    ...

webhook("sentry", event="issue.created") subscribes the workflow to the event sentry.issue.created. The source is one of standard, sentry, stripe, github, or slack. The event is the identifier within that source. The format differs per provider; see Event names below.

A single event can fan out to multiple workflows: every workflow whose trigger matches {source}.{event} starts independently.


What your workflow receives

Each matched workflow starts with one envelope as its input:

{
  "_webhook": true,
  "source": "sentry",
  "integration_id": "int_abc123",
  "event_type": "sentry.issue.created",
  "idempotency_key": "req_9f3c…",
  "timestamp": 1733337600,
  "headers": { "sentry-hook-resource": "issue", "request-id": "req_9f3c…" },
  "body": "{\"action\":\"created\",\"data\":{ … }}"
}

body is the raw request body as a string. Parse it yourself so you operate on exactly the bytes that were signature-verified. headers keys are lowercased. idempotency_key is the provider’s stable per-delivery id (absent for Slack, see Delivery semantics).


Set up an integration

You connect the sending side in Studio, once per source:

  1. Studio → Integrations → New, then pick a source.
  2. Choose the environment whose deployment should receive the triggers.
  3. Provide the signing secret. For GitHub and Standard Webhooks, AGNT5 generates the secret. Copy it into the publisher. For Stripe, Slack, and Sentry, paste the secret the provider issues; generating one here would never match.
  4. Copy the webhook URL Studio shows (…/v1/webhooks/{source}/{integration_id}) into the provider’s webhook settings.

Provider-by-provider walkthroughs live under Integrations → Event sources.


Signature verification

Every delivery must carry a valid signature; AGNT5 rejects unsigned or mismatched requests with 401 before any workflow runs. Each source uses its provider’s native HMAC-SHA256 scheme:

Source Signature header Signed payload Replay window
standard webhook-signature (v1,<base64>) {id}.{timestamp}.{body} 5 min
sentry sentry-hook-signature (hex) raw body n/a
stripe Stripe-Signature (t=…,v1=…) {timestamp}.{body} 5 min
github X-Hub-Signature-256 (sha256=…) raw body n/a
slack X-Slack-Signature (v0=…) + timestamp v0:{timestamp}:{body} 5 min

Standard Webhooks and Stripe accept several signatures on one delivery, so you can rotate signing keys without dropping events.


Delivery semantics

Webhook delivery is at-least-once. Publishers retry on any non-2xx response, and AGNT5 collapses retries using the provider’s per-delivery idempotency key: webhook-id for Standard Webhooks, Request-ID for Sentry, the event id for Stripe, X-GitHub-Delivery for GitHub. A retried delivery replays the original run instead of starting a new one.

Two cases fall outside that deduplication, and either can start a workflow more than once for the same event:

  • Slack carries no stable per-delivery id, so Slack retries are not de-duplicated.
  • The idempotency cache is per–gateway instance and time-bounded. A retry that arrives after a gateway restart, or lands on a different gateway in a multi-node deployment, sees a cold cache.
Warning: Make webhook-triggered workflows idempotent. Key your side effects off event_type and idempotency_key (or an id inside the body) so a re-delivery is a no-op. AGNT5 guarantees a workflow runs at least once per event, not exactly once.

Event names

The event your trigger matches is always {source}.{event}. What you pass as event depends on the source:

Source event you pass Resulting event name
standard the webhook-event header value standard.<event>
sentry <resource>.<action> sentry.issue.created
stripe the body type stripe.payment_intent.succeeded
github <event>.<action>, or just <event> github.issues.opened
slack the event-callback type slack.app_mention

© 2026 AGNT5
llms.txt