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 403Flask 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", 200Webhook 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#
| Symptom | Likely cause |
|---|---|
| Webhook deliveries missing | URL not HTTPS, or endpoint returned non-2xx; check webhook DLQ |
| Signature verification fails | signing_secret not saved after first setup; rotate with rotate_secret=true |
X-AgentMail-Unsigned: 1 header | No signing secret configured; call PUT /webhook to set one |