Webhooks
Webhooks let you receive real-time notifications when events occur in AutopayOS. Use them to update your systems, trigger workflows, or alert users.
Overview
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ AutopayOS │────▶│ Webhook │────▶│ Your Server │ │ Gateway │ │ Delivery │ │ │ └──────────────┘ └──────────────┘ └──────────────┘
When an event occurs (payment authorized, intent denied, etc.), AutopayOS sends an HTTP POST request to your configured endpoint.
Configure webhooks
Dashboard
- Go to Dashboard → Settings → Webhooks
- Click Add Endpoint
- Enter your endpoint URL
- Select events to subscribe to
- Copy the signing secret
API
curl https://api.autopayos.com/ap2/admin/webhooks \
-H "Authorization: Bearer $AUTOPAYOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/autopayos",
"events": [
"payment.authorized",
"payment.captured",
"payment.failed",
"intent.denied"
],
"secret": "whsec_your_secret"
}'Event types
Payment events
| Event | Description |
|---|---|
payment.authorized | Payment was authorized by rail |
payment.captured | Funds were captured |
payment.failed | Payment failed |
payment.refunded | Payment was refunded |
payment.disputed | Chargeback initiated |
Intent events
| Event | Description |
|---|---|
intent.issued | Intent was approved |
intent.denied | Intent was rejected by policy |
intent.expired | Intent exceeded time limit |
Cart events
| Event | Description |
|---|---|
cart.validated | Cart verification passed |
cart.rejected | Cart verification failed |
Agent events
| Event | Description |
|---|---|
agent.created | New agent registered |
agent.revoked | Agent was revoked |
Policy events
| Event | Description |
|---|---|
policy.updated | Policy was modified |
policy.violation | Policy violation detected |
Webhook payload
All webhooks follow this structure:
{
"id": "evt_abc123xyz",
"type": "payment.authorized",
"created": "2025-12-17T10:00:00Z",
"data": {
"mandateId": "pm_xyz789",
"amount": 38.85,
"currency": "USD",
"merchant": "amazon.com",
"rail": "STRIPE",
"stripePaymentIntentId": "pi_3abc123..."
},
"context": {
"agentDid": "did:key:z6Mk...",
"principalDid": "did:key:z6Mp...",
"intentId": "intent_abc123"
}
}| Field | Description |
|---|---|
id | Unique event identifier |
type | Event type |
created | ISO 8601 timestamp |
data | Event-specific payload |
context | Related identifiers |
Verify signatures
Always verify webhook signatures to ensure authenticity:
<tabs> <tab title="TypeScript">import crypto from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expectedSignature}`)
);
}
// Express example
app.post('/webhooks/autopayos', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-autopayos-signature'] as string;
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
// Process event
handleWebhookEvent(event);
res.status(200).send('OK');
});import hmac
import hashlib
from flask import Flask, request
app = Flask(__name__)
def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, f"sha256={expected}")
@app.route('/webhooks/autopayos', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Autopayos-Signature')
payload = request.get_data()
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.get_json()
# Process event
handle_webhook_event(event)
return 'OK', 200Handle events
Payment authorized
async function handlePaymentAuthorized(event: WebhookEvent) {
const { mandateId, amount, merchant } = event.data;
// Update order status
await db.orders.update({
where: { mandateId },
data: { status: 'AUTHORIZED' },
});
// Notify user
await sendNotification(event.context.principalDid, {
title: 'Payment Authorized',
body: `$${amount} payment to ${merchant} was authorized`,
});
}Payment captured
async function handlePaymentCaptured(event: WebhookEvent) {
const { mandateId, amount } = event.data;
await db.orders.update({
where: { mandateId },
data: {
status: 'PAID',
paidAt: new Date(),
},
});
// Trigger fulfillment
await fulfillmentService.startFulfillment(mandateId);
}Payment failed
async function handlePaymentFailed(event: WebhookEvent) {
const { mandateId, error } = event.data;
await db.orders.update({
where: { mandateId },
data: {
status: 'FAILED',
failureReason: error.code,
},
});
// Alert user
await sendNotification(event.context.principalDid, {
title: 'Payment Failed',
body: `Your payment could not be processed: ${error.message}`,
});
}Intent denied
async function handleIntentDenied(event: WebhookEvent) {
const { reasonCodes, request } = event.data;
// Log for analytics
await analytics.track('intent_denied', {
agentDid: event.context.agentDid,
reasonCodes,
amount: request.maxAmount,
merchant: request.vendorHints?.domain,
});
// Alert if anomaly
if (reasonCodes.includes('ANOMALY_DETECTED')) {
await alertSecurityTeam(event);
}
}Policy violation
async function handlePolicyViolation(event: WebhookEvent) {
const { violationType, details } = event.data;
await db.alerts.create({
data: {
type: 'POLICY_VIOLATION',
agentDid: event.context.agentDid,
details: JSON.stringify(details),
},
});
// Review high-severity violations
if (violationType === 'VELOCITY_BREACH') {
await scheduleAgentReview(event.context.agentDid);
}
}Retry behavior
AutopayOS retries failed webhook deliveries:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 24 hours |
After 6 failed attempts, the webhook is marked as failed and no further retries occur.
Successful delivery
A webhook is considered successful when your endpoint returns:
- HTTP 200-299 status code
- Within 30 seconds
Failed delivery
A webhook fails if:
- HTTP 4xx or 5xx status code
- Connection timeout (30 seconds)
- SSL/TLS error
- DNS resolution failure
Idempotency
Webhooks may be delivered multiple times. Use the event id for idempotency:
async function handleWebhookEvent(event: WebhookEvent) {
// Check if already processed
const existing = await db.processedEvents.findUnique({
where: { eventId: event.id },
});
if (existing) {
console.log('Event already processed, skipping');
return;
}
// Process event
await processEvent(event);
// Mark as processed
await db.processedEvents.create({
data: { eventId: event.id, processedAt: new Date() },
});
}Testing webhooks
Stripe CLI style testing
Use the AutopayOS CLI to forward webhooks locally:
# Install CLI
npm install -g @autopayos/cli
# Forward webhooks to local server
autopayos webhooks listen --forward-to localhost:3000/webhooks/autopayosTrigger test events
# Trigger a test event
autopayos webhooks trigger payment.authorized
# With custom data
autopayos webhooks trigger payment.authorized \
--data '{"amount": 100, "currency": "USD"}'Webhook logs
View recent webhook deliveries:
curl "https://api.autopayos.com/ap2/admin/webhooks/logs?limit=20" \
-H "Authorization: Bearer $AUTOPAYOS_API_KEY"Response:
{
"logs": [
{
"id": "whl_abc123",
"eventType": "payment.authorized",
"url": "https://your-server.com/webhooks/autopayos",
"status": 200,
"duration": 145,
"deliveredAt": "2025-12-17T10:00:00Z"
}
]
}Best practices
1. Respond quickly
Return 200 immediately, process asynchronously:
app.post('/webhooks/autopayos', async (req, res) => {
// Verify signature
if (!verifySignature(req)) {
return res.status(401).send('Invalid signature');
}
// Queue for processing
await queue.add('webhook', req.body);
// Return immediately
res.status(200).send('OK');
});
// Process asynchronously
queue.process('webhook', async (job) => {
await handleWebhookEvent(job.data);
});2. Handle duplicates
const processedEvents = new Set<string>();
function handleEvent(event: WebhookEvent) {
if (processedEvents.has(event.id)) {
return; // Already processed
}
// Process...
processedEvents.add(event.id);
}3. Verify signatures
Always verify the X-Autopayos-Signature header.
4. Use HTTPS
Only use HTTPS endpoints in production.
5. Monitor delivery
Set up alerts for failed webhook deliveries.
Event reference
payment.authorized
{
"type": "payment.authorized",
"data": {
"mandateId": "pm_xyz789",
"intentId": "intent_abc123",
"amount": 38.85,
"currency": "USD",
"merchant": "amazon.com",
"rail": "STRIPE",
"railTransactionId": "pi_3abc123..."
}
}payment.captured
{
"type": "payment.captured",
"data": {
"mandateId": "pm_xyz789",
"amount": 38.85,
"currency": "USD",
"capturedAt": "2025-12-17T10:00:15Z"
}
}intent.denied
{
"type": "intent.denied",
"data": {
"mandateId": "intent_abc123",
"reasonCodes": ["AMOUNT_OVER_CAP", "MERCHANT_NOT_ALLOWED"],
"request": {
"maxAmount": 500.00,
"merchantDomain": "casino.com"
},
"policyId": "pol_xyz"
}
}Next steps
- Evidence chain — Query event history
- Building agents — React to webhook events
- API Reference — Webhook endpoint docs