Webhook Integration Guide
Receive real-time notifications when governance events happen — transaction approvals, rejections, and budget threshold alerts. All webhooks are HMAC-SHA256 signed for verification.
Why Use Webhooks?
Without webhooks, you'd have to poll the API or check the dashboard manually. Webhooks push events to your server in real-time, enabling:
- Slack/Discord alerts when agents spend money or get rejected
- Proactive budget management — top up before agents get blocked
- Audit logging to your own systems
- Automated workflows triggered by spending events
Event Types
| Event | Fires When | Use Case |
|---|---|---|
| transaction.approved | A spend is approved | Log to Slack, update internal dashboards, trigger workflows |
| transaction.rejected | A spend is rejected | Alert on anomalous behavior, debug mandate rules |
| mandate.budget.warning | Budget usage reaches 80% | Notify team to top up before agents are blocked |
| mandate.budget.exhausted | Budget usage reaches 100% | Know immediately when an agent can no longer spend |
Events fire on all payment paths — /evaluate, x402, ACP, and Stripe CC.
Setup
Option A: Dashboard
- Go to app.quetra.dev → Settings → Webhooks
- Click Configure and enter your endpoint URL
- Click Save — events will start firing immediately
Option B: REST API
curl -X PATCH https://gateway.quetra.dev/api/v1/organization \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"webhookUrl": "https://your-app.com/webhooks/quetra"}'Signing & Verification
Every webhook includes an X-Quetra-Signature header containing an HMAC-SHA256 hash of the request body, signed with your webhook secret. Always verify this server-side to ensure the payload is genuinely from QuetraAI.
Generate a Signing Secret
Via dashboard: Settings → Webhooks → Rotate Secret. Or via API:
curl -X POST https://gateway.quetra.dev/api/v1/organization/webhook-secret/rotate \ -H "Authorization: Bearer sk_your_api_key"
Verify in Your Server
import { createHmac } from "crypto";
function verifyWebhook(
body: string,
signature: string,
secret: string
): boolean {
const expected = createHmac("sha256", secret)
.update(body)
.digest("hex");
return signature === expected;
}
// In your webhook handler:
app.post("/webhooks/quetra", (req, res) => {
const signature = req.headers["x-quetra-signature"];
const body = JSON.stringify(req.body);
if (!verifyWebhook(body, signature, WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
// Process the event
const { event, data } = req.body;
console.log(`${event}: agent ${data.agentId} spent ${data.amount}`);
res.status(200).send("OK");
});Example Payload
{
"event": "transaction.approved",
"timestamp": "2026-04-01T12:00:00Z",
"data": {
"transactionId": "tx_abc123",
"agentId": "a1b2c3d4-...",
"mandateId": "m5e6f7g8-...",
"amount": 500,
"vendor": "api.example.com",
"category": "research",
"remainingBudget": 4500,
"decision": "approved"
}
}Budget Alert Payloads
// mandate.budget.warning (≥80%)
{
"event": "mandate.budget.warning",
"timestamp": "2026-04-01T12:05:00Z",
"data": {
"mandateId": "m5e6f7g8-...",
"agentId": "a1b2c3d4-...",
"budgetTotal": 5000,
"budgetSpent": 4200,
"percentUsed": 84
}
}
// mandate.budget.exhausted (≥100%)
{
"event": "mandate.budget.exhausted",
"timestamp": "2026-04-01T12:10:00Z",
"data": {
"mandateId": "m5e6f7g8-...",
"agentId": "a1b2c3d4-...",
"budgetTotal": 5000,
"budgetSpent": 5000,
"percentUsed": 100
}
}Delivery & Retries
- Delivery is asynchronous via Cloudflare Workers
waitUntil()— it never blocks the evaluation response - 4 retry attempts with exponential backoff on failure
- 5-second timeout per delivery attempt
- All delivery attempts (success and failure) are logged in the Webhook Delivery Log on the Settings page
- Your endpoint must return 2xx to acknowledge receipt
Troubleshooting
Not receiving events
- Verify webhook URL is saved in Settings
- Check your endpoint is publicly accessible (not localhost)
- Review the Webhook Delivery Log for failed attempts
Signature verification failing
- Ensure you're hashing the raw request body (not a parsed/re-serialized version)
- Verify the secret matches what was returned from the rotate endpoint
- Check that you haven't rotated the secret since the event was sent
Related
- REST API Reference — webhook configuration endpoints
- Quickstart — get set up with the SDK first
- Dashboard — configure webhooks and view delivery log