Webhooks¶
Webhooks enable asynchronous response delivery for long-running agent tasks.
Overview¶
When you include a webhook_url in your request:
- API returns immediately with
202 Accepted - Agent processes the request asynchronously
- Response is delivered to your webhook URL
- Retries on failure (up to 4 attempts)
Setting Up Webhooks¶
Per-Request Webhook¶
Include webhook_url in your message request:
curl -X POST https://agents.ag2trust.com/api/v1/agents/{id}/messages \
-H "X-API-Key: cust_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"message": "Analyze this document...",
"webhook_url": "https://your-server.com/webhooks/ag2trust"
}'
Default Webhook¶
Set a default webhook URL for all async responses:
curl -X PUT https://agents.ag2trust.com/api/v1/webhook \
-H "X-API-Key: cust_your_api_key" \
-H "Content-Type: application/json" \
-d '{"webhook_url": "https://your-server.com/webhooks/ag2trust"}'
Webhook Delivery¶
Request Format¶
AG2Trust sends a POST request to your webhook URL:
POST /webhooks/ag2trust HTTP/1.1
Host: your-server.com
Content-Type: application/json
X-Webhook-Signature: sha256=abc123...
X-Callback-ID: cb_xyz789
{
"callback_id": "cb_xyz789",
"message_id": "msg_abc123",
"session_id": "session_456",
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2025-01-15T10:30:15Z",
"status": "completed",
"content": [
{
"type": "text",
"text": "The analysis shows..."
}
],
"metadata": {
"tokens_used": 250,
"model": "gpt-4o",
"duration_ms": 5430
}
}
Payload Fields¶
| Field | Type | Description |
|---|---|---|
callback_id | string | Unique ID matching initial response |
message_id | string | Message identifier |
session_id | string | Session ID (if provided in request) |
agent_id | string | Agent that processed the request |
timestamp | string | ISO 8601 completion timestamp |
status | string | completed or failed |
content | array | Response content blocks |
metadata | object | Processing metadata |
Signature Verification¶
Always verify webhook signatures to ensure authenticity.
Signature Header¶
Verification Algorithm¶
- Get the raw request body
- Compute HMAC-SHA256 with your API key as secret
- Compare with signature header (timing-safe)
Implementation Examples¶
import hmac
import hashlib
def verify_webhook(request_body: bytes, signature: str, api_key: str) -> bool:
"""Verify webhook signature."""
expected = hmac.new(
api_key.encode(),
request_body,
hashlib.sha256
).hexdigest()
provided = signature.replace("sha256=", "")
return hmac.compare_digest(expected, provided)
# Flask example
@app.route("/webhooks/ag2trust", methods=["POST"])
def handle_webhook():
signature = request.headers.get("X-Webhook-Signature", "")
if not verify_webhook(request.data, signature, API_KEY):
return "Invalid signature", 401
payload = request.json
# Process the webhook...
return "OK", 200
const crypto = require('crypto');
function verifyWebhook(body, signature, apiKey) {
const expected = crypto
.createHmac('sha256', apiKey)
.update(body)
.digest('hex');
const provided = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(provided)
);
}
// Express example
app.post('/webhooks/ag2trust', express.raw({type: '*/*'}), (req, res) => {
const signature = req.headers['x-webhook-signature'] || '';
if (!verifyWebhook(req.body, signature, API_KEY)) {
return res.status(401).send('Invalid signature');
}
const payload = JSON.parse(req.body);
// Process the webhook...
res.status(200).send('OK');
});
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
func verifyWebhook(body []byte, signature, apiKey string) bool {
mac := hmac.New(sha256.New, []byte(apiKey))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
provided := strings.TrimPrefix(signature, "sha256=")
return hmac.Equal([]byte(expected), []byte(provided))
}
Retry Logic¶
Failed deliveries are retried with exponential backoff:
| Attempt | Delay | Total Wait |
|---|---|---|
| 1 | Immediate | 0s |
| 2 | 1 second | 1s |
| 3 | 5 seconds | 6s |
| 4 | 30 seconds | 36s |
| 5 | 5 minutes | 5m 36s |
Failure Conditions¶
- HTTP status 4xx or 5xx
- Connection timeout (10 seconds)
- DNS resolution failure
- SSL/TLS errors
After All Retries Fail¶
The callback is marked as failed in the database. You can query failed callbacks through the Dashboard.
Testing Webhooks¶
Test Endpoint¶
curl -X POST https://agents.ag2trust.com/api/v1/webhook/test \
-H "X-API-Key: cust_your_api_key" \
-H "Content-Type: application/json" \
-d '{"webhook_url": "https://your-server.com/webhooks/ag2trust"}'
Response:
Local Development¶
For local testing, use a tunneling service:
# Using ngrok
ngrok http 3000
# Use the ngrok URL as your webhook
webhook_url: https://abc123.ngrok.io/webhooks/ag2trust
Webhook Requirements¶
URL Requirements¶
| Requirement | Details |
|---|---|
| Protocol | HTTPS only (no HTTP) |
| Port | 443 (standard HTTPS) |
| Response | Return 2xx within 10 seconds |
| Accessibility | Public internet accessible |
Expected Response¶
Return 200 OK to acknowledge receipt:
Any 2xx response is considered successful.
Handling Webhooks¶
Idempotency¶
Webhooks may be delivered more than once. Implement idempotency:
processed_callbacks = set() # In production, use Redis/database
@app.route("/webhooks/ag2trust", methods=["POST"])
def handle_webhook():
payload = request.json
callback_id = payload["callback_id"]
# Check if already processed
if callback_id in processed_callbacks:
return "Already processed", 200
# Process the webhook
process_callback(payload)
# Mark as processed
processed_callbacks.add(callback_id)
return "OK", 200
Async Processing¶
For complex handling, acknowledge quickly and process async:
from celery import Celery
celery = Celery('tasks')
@app.route("/webhooks/ag2trust", methods=["POST"])
def handle_webhook():
# Verify signature first
if not verify_signature(request):
return "Invalid", 401
# Queue for async processing
process_webhook.delay(request.json)
# Return immediately
return "OK", 200
@celery.task
def process_webhook(payload):
# Heavy processing here
...
Webhook Status Codes¶
Your endpoint should return:
| Code | Meaning | AG2Trust Action |
|---|---|---|
| 200-299 | Success | Mark delivered |
| 400-499 | Client error | Retry (bug in payload unlikely) |
| 500-599 | Server error | Retry |
| Timeout | No response in 10s | Retry |
Best Practices¶
1. Always Verify Signatures¶
2. Respond Quickly¶
# Good: Acknowledge and process async
@app.route("/webhook")
def webhook():
queue.add(request.json)
return "OK", 200
# Bad: Process in request
@app.route("/webhook")
def webhook():
heavy_processing(request.json) # May timeout!
return "OK", 200
3. Handle All Status Types¶
payload = request.json
if payload["status"] == "completed":
handle_success(payload["content"])
elif payload["status"] == "failed":
handle_failure(payload)
4. Log Callback IDs¶
import logging
@app.route("/webhook")
def webhook():
payload = request.json
logging.info(f"Received callback: {payload['callback_id']}")
# Process...
5. Set Up Monitoring¶
Monitor your webhook endpoint for:
- Response times
- Error rates
- Retry patterns
- Processing delays
Next Steps¶
- Rate Limits - Understanding limits
- Error Codes - Error handling
- Webhook Integration Guide - Complete setup guide