Webhooks

Get notified instantly when a form is submitted. AgentForms sends a signed POST request to your callback_url with the full response data.

Setting Up Webhooks

Pass a callback_url when creating a form. Optionally include callback_metadata to attach context (e.g. conversation ID, task ID) that will be echoed back in the webhook payload.

Python
form = client.create_form(
    title="Approval Request",
    fields=[...],
    callback_url="https://your-app.com/webhooks/agentforms",
    callback_metadata={
        "conversation_id": "conv_abc123",
        "task": "approval",
    },
)

A webhook secret (64-character hex string) is auto-generated by the server whenever you provide a callback_url. This secret is used to sign the payload but is never exposed via the API.

Webhook Payload

When a form is submitted, AgentForms sends a POST request to your callback_url with this JSON body:

JSON
{
  "event": "form.completed",
  "form_id": "01JNXXXXXXXXXXXXXXXXXX",
  "form_title": "Approval Request",
  "completed_at": "2026-03-05T12:34:56Z",
  "response_id": "01JNYYYYYYYYYYYYYYY",
  "data": {
    "approved": true,
    "comment": "Looks good to me"
  },
  "metadata": {
    "ip": "203.0.113.42",
    "user_agent": "Mozilla/5.0..."
  },
  "callback_metadata": {
    "conversation_id": "conv_abc123",
    "task": "approval"
  }
}
FieldTypeDescription
eventstringAlways "form.completed"
form_idstringULID of the form
form_titlestringForm title
completed_atstringISO 8601 timestamp
response_idstringULID of the response
dataobjectField key → submitted value
metadataobjectSubmitter IP and user agent
callback_metadataobjectYour custom metadata (pass-through from form creation)

Request Details

PropertyValue
MethodPOST
Content-Typeapplication/json
Timeout10 seconds (configurable via AGENTFORMS_WEBHOOK_TIMEOUT)

Signature Verification

Every webhook request includes an HMAC-SHA256 signature in the X-AgentForms-Signature header:

HTTP Header
X-AgentForms-Signature: sha256=a1b2c3d4e5f6...

Verify this signature to ensure the request came from AgentForms and wasn't tampered with.

Python

Python
import hashlib, hmac, json

def verify_signature(body: bytes, secret: str, signature_header: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(
        f"sha256={expected}",
        signature_header,
    )

# In your webhook handler:
if not verify_signature(
    request.body,
    WEBHOOK_SECRET,
    request.headers["X-AgentForms-Signature"],
):
    raise ValueError("Invalid signature")

Node.js

JavaScript
const crypto = require("crypto");

function verifySignature(body, secret, signatureHeader) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(`sha256=${expected}`),
    Buffer.from(signatureHeader),
  );
}

// In your Express handler:
app.post("/webhooks/agentforms", (req, res) => {
  const sig = req.headers["x-agentforms-signature"];
  if (!verifySignature(req.rawBody, WEBHOOK_SECRET, sig)) {
    return res.status(401).send("Invalid signature");
  }
  const payload = JSON.parse(req.rawBody);
  // Handle the webhook...
  res.sendStatus(200);
});

Retry Behavior

If your endpoint returns a non-2xx status code or the request times out, AgentForms retries with exponential backoff:

AttemptDelay Before
1st (initial)Immediate
2nd (retry 1)1 second
3rd (retry 2)5 seconds
4th (retry 3)25 seconds

After all retries are exhausted, the webhook delivery is abandoned. The response data is still available via the List Responses API endpoint.

Tip: Always return a 200 status quickly from your webhook handler. Do any heavy processing asynchronously to avoid timeouts.

Configuration

Webhook behavior can be tuned with environment variables on the AgentForms server:

VariableDefaultDescription
AGENTFORMS_WEBHOOK_TIMEOUT10HTTP timeout in seconds for webhook delivery
AGENTFORMS_WEBHOOK_MAX_RETRIES3Maximum number of retry attempts