OpenAPI JSONDashboard

Guides

Webhooks

What this does#

Webhooks deliver mail.received events asynchronously to your HTTPS endpoint with HMAC-SHA256 signatures, so your agent can react immediately without polling.

Notification rules#

Use GET /notifications and PUT /notifications when you want per-rule filtering and multiple delivery channels. Each rule can route matched inbound events to a webhook, one or more email recipients, or both.

python
result = client.put_notifications([
    {
        "id": "marketplace-alerts",
        "name": "Marketplace alerts",
        "enabled": True,
        "filters": {
            "sender_verified": True,
            "traffic_types": ["marketplace"],
            "sender_domains": ["partner.com"],
            "trust_tiers": ["verified"],
        },
        "channels": {
            "webhook": {
                "enabled": True,
                "url": "https://example.com/agentmail/webhook",
            },
            "email": {
                "enabled": True,
                "recipients": ["me@personal.com", "ops@company.com"],
            },
        },
    }
])

Configure a webhook#

Python
result = client.put_webhook(
    url="https://example.com/agentmail/webhook",
    enabled=True,
)
# Save the signing_secret — it is returned only on first setup or explicit rotation
signing_secret = result.get("signing_secret")

Inspect, rotate, and delete#

python
# Inspect
cfg = client.get_webhook()
print(cfg["url"], cfg["enabled"])

# Rotate
result = client.put_webhook(
    url="https://example.com/agentmail/webhook",
    enabled=True,
    rotate_secret=True,
)
new_secret = result["signing_secret"]   # save immediately

# Delete
client.delete_webhook()

Verify the signature#

Every delivery includes two headers:

text
X-AgentMail-Timestamp: <unix epoch>
X-AgentMail-Signature: v1=<hex>

The signature is HMAC-SHA256 over <timestamp>.<raw_body>.

Python SDK:

python
from agentmail_client import AgentMailClient

valid = AgentMailClient.verify_webhook_signature(
    signing_secret=os.environ["WEBHOOK_SIGNING_SECRET"],
    timestamp=request.headers["X-AgentMail-Timestamp"],
    raw_body=request.body.decode("utf-8"),
    signature_header=request.headers["X-AgentMail-Signature"],
    max_skew_seconds=300,   # reject replays older than 5 min
)
if not valid:
    return 403

Flask example:

python
@app.post("/agentmail/webhook")
def handle_webhook():
    raw = request.get_data()
    if not AgentMailClient.verify_webhook_signature(
        signing_secret=SIGNING_SECRET,
        timestamp=request.headers.get("X-AgentMail-Timestamp", ""),
        raw_body=raw.decode("utf-8"),
        signature_header=request.headers.get("X-AgentMail-Signature", ""),
        max_skew_seconds=300,
    ):
        return "forbidden", 403

    event = request.get_json()
    agent_id   = event["agent_id"]
    message_id = event["message_id"]
    verified   = event["sender_verified"]
    # ... process event
    return "ok", 200

Webhook payload shape#

json
{
  "type": "mail.received",
  "agent_id": "my-agent",
  "message_id": "msg-abc123",
  "sender": "user@example.com",
  "subject": "Quarterly planning notes",
  "timestamp": "2026-04-16T12:00:00+00:00",
  "sender_verified": true,
  "peer_trust_tier": "verified",
  "rate_limited": false,
  "is_duplicate": false,
  "s3_bucket": "my-bucket",
  "s3_key": "inbound/my-agent/msg-abc123.eml"
}

Common failure modes#

SymptomLikely cause
Webhook deliveries missingURL not HTTPS, or endpoint returned non-2xx; check webhook DLQ
Signature verification failssigning_secret not saved after first setup; rotate with rotate_secret=true
X-AgentMail-Unsigned: 1 headerNo signing secret configured; call PUT /webhook to set one