Agent creates a form, registers a webhook to its own endpoint, and reacts when humans submit.
Updated March 2026
The webhook callback pattern lets an AI agent set up a form, point a webhook at its own server, and take action the moment a human submits data. The agent doesn't poll. It doesn't wait. It gets an HTTP POST with the submission payload and decides what to do next -- update a page, send an email, create a dashboard entry, or trigger another workflow. This is the standard pattern for agents that run a persistent server process. If your agent can receive HTTP requests, this is the fastest path from human input to agent action.
1. Create a form for human input
The agent creates a form with the fields it needs. This could be a feedback form, an approval request, a content submission -- anything where the agent needs structured input from a human.
const SUTRENA_KEY = process.env.SUTRENA_API_KEY;
const formRes = await fetch("https://sutrena.com/api/forms", {
method: "POST",
headers: {
"Authorization": `Bearer ${SUTRENA_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Agent Approval Queue",
fields: [
{ name: "item", label: "Item to approve", type: "text", required: true },
{ name: "decision", label: "Decision", type: "select", options: ["Approve", "Reject", "Needs revision"] },
{ name: "notes", label: "Notes", type: "textarea" },
],
}),
});
const form = await formRes.json();
console.log("Form URL:", form.data.hostedFormUrl);
console.log("Form ID:", form.data.formId);2. Register a webhook pointing to your agent
The agent creates a webhook that fires on every submission to its form. The webhook URL is the agent's own HTTP endpoint. When a human submits the form, Sutrena sends a POST request to this URL with the full submission payload.
const webhookRes = await fetch("https://sutrena.com/api/webhooks", {
method: "POST",
headers: {
"Authorization": `Bearer ${SUTRENA_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://my-agent.example.com/webhook/approval",
formIds: [form.data.formId],
description: "Agent approval callback",
}),
});
const webhook = await webhookRes.json();
// Save the signing secret to verify payloads later
const signingSecret = webhook.data.signingSecret;3. Handle the webhook in your agent
When a human submits the form, your agent receives an HTTP POST with the submission data. Parse the payload and take action -- update a page, send a notification, trigger a downstream workflow, or anything else your agent needs to do.
// Node.js / Express example
import express from "express";
import crypto from "crypto";
const app = express();
app.use(express.json());
app.post("/webhook/approval", async (req, res) => {
// Verify the webhook signature (recommended)
const signature = req.headers["x-webhook-signature"];
const expected = crypto
.createHmac("sha256", signingSecret)
.update(JSON.stringify(req.body))
.digest("hex");
if (signature !== expected) {
return res.status(401).json({ error: "Invalid signature" });
}
// Extract submission data
const { payload } = req.body;
const { item, decision, notes } = payload;
console.log(`Decision on "${item}": ${decision}`);
if (decision === "Approve") {
// Agent takes action: update a page, call another API, etc.
await fetch(`https://sutrena.com/api/pages/${pageId}`, {
method: "PUT",
headers: {
"Authorization": `Bearer ${SUTRENA_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
html: `<h1>Approved: ${item}</h1><p>${notes || "No notes."}</p>`,
}),
});
}
res.status(200).json({ received: true });
});
app.listen(3000);4. Python Flask alternative
The same pattern works in Python. The agent receives the webhook POST and acts on it.
from flask import Flask, request, jsonify
import hmac, hashlib, json, os, requests
app = Flask(__name__)
SIGNING_SECRET = os.environ["WEBHOOK_SIGNING_SECRET"]
SUTRENA_KEY = os.environ["SUTRENA_API_KEY"]
@app.route("/webhook/approval", methods=["POST"])
def handle_approval():
# Verify signature
signature = request.headers.get("x-webhook-signature", "")
expected = hmac.new(
SIGNING_SECRET.encode(),
request.data,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(signature, expected):
return jsonify(error="Invalid signature"), 401
data = request.json
decision = data["payload"]["decision"]
item = data["payload"]["item"]
if decision == "Approve":
requests.put(
f"https://sutrena.com/api/pages/{page_id}",
headers={"Authorization": f"Bearer {SUTRENA_KEY}"},
json={"html": f"<h1>Approved: {item}</h1>"},
)
return jsonify(received=True)The POST body contains `{ formId, submissionId, payload: { ...field values }, submittedAt }`. The payload object has keys matching your form field names. File fields contain the CDN URL of the uploaded file.
Use the polling pattern instead. See the "Poll for new form submissions" guide. Poll GET /api/forms/:id/submissions with a `from` timestamp to get new submissions since the last check.
Yes. Set `template: "slack"`, `"discord"`, `"telegram"`, `"teams"`, or `"google-chat"` to auto-format the payload for those platforms. For your own agent endpoint, use the default template which sends raw JSON.
Compute HMAC-SHA256 of the raw request body using the signing secret (returned when you create the webhook). Compare it to the `x-webhook-signature` header. This ensures the payload came from Sutrena.
Sutrena is the web runtime for AI agents. Forms, Pages, Dashboards, Analytics, Webhooks, Automations, Emails — all through 75 MCP tools and one REST API. Your agent creates web artifacts, humans interact with them, and your agent gets the data back. Use any one feature or all of them together.
Pages
Deploy HTML instantly
Forms
Collect structured data
Dashboards
Visualize with 7 widget types
Analytics
Privacy-first, no cookies
Webhooks
Slack, Discord, Telegram
1. Get a trial key (no auth, no signup)
curl -X POST https://sutrena.com/api/trial2. Create anything — a page, form, dashboard, or analytics site
# Create a form with a dashboard
curl -X POST https://sutrena.com/api/forms \
-H "Authorization: Bearer st_trial_xxx" \
-H "Content-Type: application/json" \
-d '{"workflowId": "waitlist", "createDashboard": true}'
# Or deploy a page
curl -X POST https://sutrena.com/api/pages \
-H "Authorization: Bearer st_trial_xxx" \
-H "Content-Type: application/json" \
-d '{"slug": "index", "title": "My Site", "html": "<h1>Live</h1>"}'Get a trial API key instantly with no signup, or create an account for the full experience.