Roxels/ docs
webhooks

Webhooks overview

Webhooks are how Roxels delivers structured data from a conversation to your backend, in real time, as goals commit.

When a webhook fires

A goal commits when the agent decides the conversation has captured enough about that goal. On commit, every output configured for that goal fires. Outputs can be:

  • A webhook to your URL.
  • A frontend callback — see Embed events.
  • A chained API call — a templated HTTP request to an upstream service.

You configure these per goal in the template. Multiple outputs per goal is fine.

The flow, end to end

User talks ──► Agent extracts data ──► Goal commits ──► Outputs fire

                          ┌───────────────────────────────────┼───────────────────────────────────┐
                          ▼                                   ▼                                   ▼
                   POST your_webhook                  JS event in embed               POST upstream API

What you'll receive

A typical webhook delivery looks like this:

POST /your-endpoint HTTP/1.1
Host: example.com
Content-Type: application/json
User-Agent: Roxels-Webhook/1
Idempotency-Key: commit_a1b2c3
X-Roxels-Signature: t=1716480000,v1=...
Authorization: Bearer your-shared-secret
 
{
  "session_id": "sess_xxx",
  "interview_id": "iv_xxx",
  "template_id": "tpl_aaa",
  "goal_id": "use_case",
  "committed_at": "2026-05-23T12:34:56Z",
  "data": {
    "use_case": "customer onboarding automation",
    "user_role": "engineer"
  },
  "context": {
    "plan": "pro",
    "external_id": "user_123"
  }
}

The exact shape is controlled by the body template you configure for the webhook — you can send the data raw, wrap it in your own envelope, or transform it. See Payload shape for the templating language and examples.

Setup, at a high level

  1. Configure the webhook in your template's output settings. Set the URL, method, headers, body template, and the goal that triggers it.
  2. Add a Roxels secret if your URL needs authentication. Secrets live in your org's secret store and interpolate into headers and bodies — see Secrets handling.
  3. Build your endpoint to accept the POST. Validate the signature, deduplicate by Idempotency-Key, return 2xx.
  4. Test from the dashboard — there's a "Send test" button on each output config.

Idempotency

Every webhook attempt carries an Idempotency-Key header. Same commit, same key — across retries. Your endpoint must deduplicate on this key. If you've already processed it, return 2xx immediately; don't double-act.

This is the most important rule for webhook handlers. Roxels retries on transient failures (see Retries and idempotency), and without dedup, you risk processing the same commit twice.

Signature verification

Each webhook is signed. Verify the signature before acting on the body. The header is:

X-Roxels-Signature: t=<unix-timestamp>,v1=<hex-hmac>

Compute HMAC-SHA256 over <timestamp>.<raw-body> using your webhook secret, then compare. Reject requests where the timestamp is more than a few minutes old (replay protection).

Example (Node):

import crypto from "node:crypto";
 
function verify(req, secret) {
  const header = req.headers["x-roxels-signature"];
  const [tPart, sigPart] = header.split(",");
  const t = tPart.split("=")[1];
  const sig = sigPart.split("=")[1];
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${req.rawBody}`).digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    throw new Error("invalid signature");
  }
  const age = Date.now() / 1000 - parseInt(t, 10);
  if (age > 300) throw new Error("timestamp too old");
}

Secrets

Webhooks often need to authenticate to your backend (a bearer token, an API key, an HMAC secret). Roxels has a built-in secret store so these values never have to live in your template config in plaintext.

  1. Add a secret in the dashboard at Settings → Secrets.
  2. Reference it in webhook headers or body: {{secret:abc-123}} (or by name).
  3. At fire time, Roxels swaps in the actual value.

The secret value never leaves the Roxels backend — it's not visible in template exports, audit logs, or dashboard listings after creation.

Response handling

Roxels expects a 2xx response within a reasonable timeout. Anything else is treated as a failure and may be retried — see Retries and idempotency.

Don't return useful information in the response body — Roxels doesn't parse it. If your webhook needs to "respond" with data (e.g. a customer id), use a chained API call instead, where the response is fed back into the conversation.

Chained API calls

A chained API call is like a webhook with one extra step: the agent uses the response. Pattern:

  1. Goal commits → chained API fires.
  2. Your upstream service responds with structured data.
  3. Roxels parses the response and makes it available to the agent under {{response.X}} for subsequent turns.

Use this when the agent needs to do something in your system mid-conversation and react to the result. Example: "look up this customer's order history" — the response is folded into the agent's working understanding.