Webhooks
Real-time message delivery via webhooks and signature verification.
Webhooks
Webhooks push new messages to your application in real-time instead of polling the API. When a message passes security checks, SecureAgentMail sends an HTTP POST to your configured webhook URL.
Setting up webhooks
Configure a webhook URL on your inbox:
curl -X PATCH https://secureagentmail.com/api/v1/inboxes/my-agent \
-H "Authorization: Bearer $SAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "webhook_url": "https://myapp.com/webhooks/email" }'Webhook payload
SecureAgentMail sends a JSON payload for each new message:
{
"event": "message.received",
"timestamp": "2026-02-25T10:02:15Z",
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"inbox_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"from": "[email protected]",
"to": "[email protected]",
"subject": "Hello",
"body": "Message content here",
"status": "delivered",
"direction": "inbound",
"security_analysis": {
"score": 5,
"level": "safe",
"summary": "No threats detected"
},
"created_at": "2026-02-25T10:02:15Z"
}
}Signature verification
Every webhook request includes a signature header for verification:
X-SAM-Signature: sha256=a1b2c3d4e5f6...
X-SAM-Timestamp: 1709125335Verifying the signature
- Get your webhook secret from Settings → Webhooks in the dashboard
- Construct the signed payload:
{timestamp}.{raw_request_body} - Compute HMAC-SHA256 with your webhook secret
- Compare with the
X-SAM-Signatureheader value
import crypto from "crypto";
function verifyWebhook(req, secret) {
const signature = req.headers["x-sam-signature"];
const timestamp = req.headers["x-sam-timestamp"];
const body = req.body; // raw string
// Reject requests older than 5 minutes
const age = Date.now() / 1000 - parseInt(timestamp);
if (age > 300) {
throw new Error("Webhook timestamp too old");
}
const payload = `${timestamp}.${body}`;
const expected = "sha256=" +
crypto.createHmac("sha256", secret).update(payload).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
throw new Error("Invalid webhook signature");
}
return JSON.parse(body);
}import hmac
import hashlib
import time
def verify_webhook(headers, body, secret):
signature = headers["X-SAM-Signature"]
timestamp = headers["X-SAM-Timestamp"]
# Reject old requests
if time.time() - int(timestamp) > 300:
raise ValueError("Webhook timestamp too old")
payload = f"{timestamp}.{body}"
expected = "sha256=" + hmac.new(
secret.encode(), payload.encode(), hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
raise ValueError("Invalid webhook signature")Retry behavior
SecureAgentMail retries failed webhook deliveries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 30 seconds |
| 3 | 2 minutes |
| 4 | 10 minutes |
| 5 | 1 hour |
After 5 failed attempts, the message is marked as webhook_failed and can
be retrieved via the API. Webhook failures are logged in the audit trail.
Withheld messages
Messages withheld by security analysis (risk score > 70) are not sent to
your webhook. They remain in withheld status until manually released:
# Release via API
curl -X POST https://secureagentmail.com/api/v1/messages/{id}/release \
-H "Authorization: Bearer $SAM_API_KEY"
# Release via CLI
sam messages release <message-id>Once released, the message is delivered to your webhook as normal.
Best practices
- Always verify signatures — Never process unverified webhook payloads
- Respond quickly — Return
200 OKwithin 5 seconds, process asynchronously - Handle duplicates — Use the message
idfor idempotency - Use HTTPS — Webhook URLs must use HTTPS in production
- Monitor failures — Check the audit log for
webhook_failedevents