Skip to content

Webhook events

agentinbox.pro delivers inbound mail as JSON POST requests to your configured webhookUrl. This is the primary consumption path — the events API is for inspection and debugging.

POST https://your-agent.example.com/webhooks/inbound-email
Content-Type: application/json
X-AgentMail-Event-Id: 05df8afb-b4bd-4f4c-87e9-334594b9accc
X-AgentMail-Timestamp: 1718246400
X-AgentMail-Signature: t=1718246400,v1=<hmac_hex>
{
"event": "inbound.email.received",
"event_id": "05df8afb-b4bd-4f4c-87e9-334594b9accc",
"inbound_email_id": "05df8afb-b4bd-4f4c-87e9-334594b9accc",
"idempotency_key": "a1b2c3...",
"message_id": "<vendor-123@example.com>",
"domain": "acme-agent.com",
"recipient": "billing@acme-agent.com",
"from": "vendor@example.com",
"subject": "Invoice #123",
"text": "Attached is your invoice.",
"html": null,
"attachments": [],
"receivedAt": "2026-06-13T01:20:00.000Z",
"rawRef": "inbound/<domain-id>/<uuid>.eml"
}
FieldDescription
eventAlways inbound.email.received today
event_idStable identifier — use for deduplication
idempotency_keyHash of domain + message_id
message_idParsed RFC Message-ID header, if present
domainVerified recipient domain
recipientFull SMTP address — route agents on this
fromSender address
subjectEmail subject
text / htmlParsed body
attachmentsMetadata only (filename, contentType, size)
rawRefPointer to stored raw .eml

Return any HTTP 2xx:

{ "ok": true }

Non-2xx responses are recorded as failed delivery. agentinbox.pro retries with backoff; persistent failures become dead letters.

Sign secret: workspace webhook_signing_secret (whsec_...) from POST /api/workspaces.

HMAC-SHA256(secret, "{timestamp}.{raw_body}")
Header: X-AgentMail-Signature: t=<timestamp>,v1=<hex>

Reject when:

  • timestamp is outside ±5 minutes
  • HMAC does not match
  • X-AgentMail-Event-Id ≠ payload event_id
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyAgentMailWebhook(rawBody, headers, secret) {
const timestamp = headers["x-agentmail-timestamp"];
const signature = headers["x-agentmail-signature"];
if (!timestamp || !signature) return false;
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
if (age > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const match = signature.match(/^t=\d+,v1=([a-f0-9]+)$/);
if (!match) return false;
return timingSafeEqual(Buffer.from(match[1]), Buffer.from(expected));
}
import hmac
import hashlib
import re
import time
def verify_agentmail_webhook(raw_body: bytes, headers: dict, secret: str) -> bool:
timestamp = headers.get("x-agentmail-timestamp")
signature = headers.get("x-agentmail-signature")
if not timestamp or not signature:
return False
if abs(int(time.time()) - int(timestamp)) > 300:
return False
expected = hmac.new(
secret.encode(),
f"{timestamp}.".encode() + raw_body,
hashlib.sha256,
).hexdigest()
match = re.match(r"^t=\d+,v1=([a-f0-9]+)$", signature)
return bool(match and hmac.compare_digest(match.group(1), expected))
1. Read raw body (before JSON parse) for signature check
2. Verify HMAC + timestamp + event id
3. Dedupe on event_id or idempotency_key
4. Route by recipient address
5. Return 2xx only after the event is safely accepted
GET /api/domains/{id}/delivery-attempts
Authorization: Bearer <api_key>

Replay a failed event manually:

POST /api/domains/{id}/events/{eventId}/replay
Authorization: Bearer <api_key>