How do webhook triggers work in Agent Studio?#
A Webhook node turns an agent into a listener. Your backend, a serverless function or any automation step that runs code POSTs JSON to the agent's webhook URL, Agent Studio checks the signature, and the flow runs with that JSON as its input. A plain JSON object passes through as-is; any other JSON value arrives wrapped as { body: {value} }.
Set up a webhook-triggered agent
Step 1: Add a Webhook node
Open the builder, drop a Webhook node at the start of the flow and wire it to the steps that should run. The optionalOpen the buildernotefield is for your own reference.Step 2: Dry-run the flow
Run it with a sample payload so every downstream step sees the shape it will get in production. Dry runs are free.Step 3: Launch and save the secret
Click Launch. The launch response shows the signing secret once; store it in the secret store of whatever will send the webhook.Read the launch guideStep 4: Sign and send a test
Use the Node or Python sample below with your agent's id or slug and confirm a200with arunId.
How do I sign a webhook request?#
Take the current time in Unix milliseconds, join it to the exact raw body with a period, and compute an HMAC-SHA256 of that string keyed by your webhook secret. Send the hex digest prefixed with sha256=. Sign the bytes you send: serialize the JSON once and post that same string.
import { createHmac } from "node:crypto";
const secret = process.env.SUEDE_WEBHOOK_SECRET!; // shown once at launch
const url = "https://agents.suedeai.ai/api/agents/<id-or-slug>/webhook";
export async function sendToAgent(payload: Record<string, unknown>): Promise<unknown> {
const timestamp = Date.now().toString(); // Unix ms, decimal string
const rawBody = JSON.stringify(payload); // sign exactly what you send
const signature =
"sha256=" + createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
const res = await fetch(url, {
method: "POST",
headers: {
"content-type": "application/json",
"x-suede-webhook-timestamp": timestamp,
"x-suede-webhook-signature": signature,
},
body: rawBody,
});
if (!res.ok) throw new Error(`Agent webhook failed: ${res.status}`);
return res.json(); // { runId, status, totalCostUsdc, outputs }
}
await sendToAgent({ event: "lead.created", email: "ana@example.com" });import hashlib, hmac, json, os, time, urllib.request
secret = os.environ["SUEDE_WEBHOOK_SECRET"].encode()
url = "https://agents.suedeai.ai/api/agents/<id-or-slug>/webhook"
timestamp = str(int(time.time() * 1000))
raw_body = json.dumps({"event": "lead.created", "email": "ana@example.com"})
digest = hmac.new(secret, f"{timestamp}.{raw_body}".encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request(url, data=raw_body.encode(), method="POST", headers={
"content-type": "application/json",
"x-suede-webhook-timestamp": timestamp,
"x-suede-webhook-signature": "sha256=" + digest,
})
print(urllib.request.urlopen(req).read().decode())TS=$(($(date +%s) * 1000))
BODY='{"event":"lead.created","email":"ana@example.com"}'
SIG=$(printf '%s' "$TS.$BODY" | openssl dgst -sha256 -hmac "$SUEDE_WEBHOOK_SECRET" | sed 's/^.* //')
curl -X POST https://agents.suedeai.ai/api/agents/<id-or-slug>/webhook \
-H 'content-type: application/json' \
-H "x-suede-webhook-timestamp: $TS" \
-H "x-suede-webhook-signature: sha256=$SIG" \
-d "$BODY"What headers and limits does the webhook endpoint enforce?#
| Rule | Value |
|---|---|
| Endpoint | POST /api/agents/{id-or-slug}/webhook |
| Content type | application/json (a charset suffix is fine) |
| Signature header | x-suede-webhook-signature: sha256={hex} |
| Timestamp header | x-suede-webhook-timestamp, Unix milliseconds as a decimal string |
| Signed string | {timestamp}.{raw body}, HMAC-SHA256 keyed by your secret |
| Freshness window | 5 minutes either side of the server clock |
| Body size | 256 KB maximum |
| Rate limits | Per source IP and per agent, each with a Retry-After on 429 |
What does each webhook response mean?#
| Status | Meaning | What to do |
|---|---|---|
200 | The run started and finished; body has runId, status, totalCostUsdc, outputs | Read status for done or error |
401 | Bad signature, stale timestamp or unknown agent | Check the secret, clock and raw body |
413 | Body over 256 KB | Send a reference such as a URL instead of the full payload |
415 | Content type is not JSON | Set content-type: application/json |
429 | Rate limited | Wait for Retry-After, then resend with a fresh timestamp |
A bad signature, a stale timestamp and an unknown agent all return the same 401 on purpose, so nobody probing the endpoint learns which agents exist. If a signature fails, compare the exact bytes you signed with the bytes you sent: re-serializing JSON after signing is the usual cause.
How do I rotate a lost or leaked webhook secret?#
Signed in as the owner, call POST /api/agents/{id}/webhook/rotate to replace the secret atomically and receive the new one once. Relaunching an agent never rotates the secret, so the services already sending to it keep working through every update.
Wire your first event
Drop a Webhook node on the canvas, launch, and send a signed test in under ten minutes.