SecureAgentMail

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: 1709125335

Verifying the signature

  1. Get your webhook secret from Settings → Webhooks in the dashboard
  2. Construct the signed payload: {timestamp}.{raw_request_body}
  3. Compute HMAC-SHA256 with your webhook secret
  4. Compare with the X-SAM-Signature header 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:

AttemptDelay
1Immediate
230 seconds
32 minutes
410 minutes
51 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

  1. Always verify signatures — Never process unverified webhook payloads
  2. Respond quickly — Return 200 OK within 5 seconds, process asynchronously
  3. Handle duplicates — Use the message id for idempotency
  4. Use HTTPS — Webhook URLs must use HTTPS in production
  5. Monitor failures — Check the audit log for webhook_failed events

On this page