Documentation Index
Fetch the complete documentation index at: https://conto.finance/docs/llms.txt
Use this file to discover all available pages before exploring further.
Webhooks
Conto sends HTTP POST requests to your configured URL when payments progress, an A2A payment request is created, a service spend record is written, or an agent is frozen or unfrozen.
Setup
- Go to Settings > Webhooks in the dashboard
- Enter your HTTPS endpoint URL
- Copy the generated signing secret — you’ll need this to verify payloads
Webhook URLs must use HTTPS. HTTP, localhost, and private IP ranges are blocked for security (SSRF
protection).
Event Types
| Event | Fires when |
|---|
payment.requested | A payment request is created |
payment.approved | A payment passes policy evaluation |
payment.denied | A payment is blocked by policy |
payment.executed | A payment transaction is submitted onchain |
payment.confirmed | A payment is confirmed onchain |
payment.failed | A payment transaction fails |
payment_request.created | An agent-to-agent payment request is created |
service_payment.recorded | An x402 or MPP transaction is recorded |
service_payment.failed | An x402 or MPP transaction fails |
agent.frozen | An agent is frozen |
agent.unfrozen | An agent is restored to active status |
Every webhook POST includes these headers and a JSON body:
| Header | Description |
|---|
X-Conto-Event | Event type (e.g., payment.confirmed) |
X-Conto-Timestamp | ISO 8601 timestamp of the event |
X-Conto-Signature | HMAC-SHA256 signature of the body |
X-Conto-Delivery-Attempt | Attempt number (1, 2, or 3) |
Body
{
"event": "payment.confirmed",
"timestamp": "2026-04-07T12:00:00.000Z",
"data": {
"paymentId": "req_abc123",
"amount": 50,
"transactionHash": "0xabc...",
"transactionId": "tx_xyz",
"organizationId": "org_123",
"agentId": "agent_456"
}
}
Verifying Signatures
Every payload is signed with your webhook secret using HMAC-SHA256. Verify signatures to confirm the request came from Conto.
import express from 'express';
import { createHmac, timingSafeEqual } from 'crypto';
function verifyWebhook(rawBody: string, signature: string | undefined, secret: string): boolean {
if (!signature) return false;
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
const signatureBuffer = Buffer.from(signature, 'hex');
const expectedBuffer = Buffer.from(expected, 'hex');
if (signatureBuffer.length !== expectedBuffer.length) {
return false;
}
return timingSafeEqual(signatureBuffer, expectedBuffer);
}
// In your handler
app.post('/webhooks/conto', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.header('x-conto-signature');
const rawBody = req.body.toString('utf8');
if (!verifyWebhook(rawBody, signature, process.env.WEBHOOK_SECRET!)) {
return res.status(401).send('Invalid signature');
}
const { event, data } = JSON.parse(rawBody);
switch (event) {
case 'payment.confirmed':
console.log('Payment confirmed:', data.transactionHash);
break;
case 'payment.denied':
console.log('Payment denied:', data.reasons);
break;
}
res.status(200).send('OK');
});
The same rule applies in other frameworks: verify the exact raw request body before you parse it.
Retry Behavior
Failed deliveries are retried up to 3 times with exponential backoff:
| Attempt | Timing |
|---|
| 1 | Immediate |
| 2 | ~1 second later |
| 3 | ~3 seconds after the initial delivery |
A delivery is considered failed if your endpoint returns a non-2xx status code or doesn’t respond within 10 seconds.
Agent Callback URLs
In addition to organization-level webhooks, individual agents can have a callbackUrl set during creation. When set, webhooks are delivered to both the organization URL and the agent’s callback URL.
Delivery Targets
| Target | Configured via | Receives events for |
|---|
| Organization webhook | Settings > Webhooks | All events in the org |
| Agent callback URL | Agent config | Events for that agent only |
Both targets receive the same payload format and signature headers.