Webhooks
Get notified in real-time when events happen in your AgentSend account — new emails received, deliveries confirmed, bounces detected, and more.
Overview
Instead of polling the API to check for new messages, webhooks let AgentSend push event data to your server the moment something happens. Register an HTTPS endpoint and AgentSend will send a POST request with a JSON payload whenever a subscribed event fires.
Webhooks are the recommended way to build reactive agents. When a reply lands in your agent's inbox, your application receives the full message payload within seconds and can act on it immediately.
Webhook endpoints must be publicly accessible over HTTPS. Localhost URLs are not accepted. Use a tool like ngrok or a tunnel service when developing locally.
Creating a Webhook
Register a new webhook by sending a POST request to /webhooks. You must provide a url field; all other fields are optional.
/webhooks
| Parameter | Type | Description |
|---|---|---|
url required |
string | The HTTPS URL AgentSend will POST events to. |
description |
string | A human-readable label to identify the webhook in the dashboard. |
events |
string[] | List of event types to subscribe to. Omit to receive all events. |
const res = await fetch("https://api.agentsend.io/webhooks", { method: "POST", headers: { "x-api-key": process.env.AGENTSEND_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://myapp.example.com/hooks/agentsend", description: "Production inbox events", events: ["message.received", "message.bounced"], }), }); const webhook = await res.json(); console.log(webhook.id); // wh_01j9... console.log(webhook.secret); // whsec_... store this securely!
The secret is returned only once in the creation response. Store it in a secure environment variable immediately — it cannot be retrieved again, but you can rotate it via PUT /webhooks/{id}.
Event Types
Subscribe to specific events by passing their identifiers in the events array. If you omit the array, AgentSend delivers all event types to your endpoint.
| Event | Description |
|---|---|
domain.verified |
A custom domain has passed DNS verification and is ready to use. |
message.received |
An inbound email has arrived in one of your agent's inboxes. |
message.sent |
An outbound message has been accepted and queued for delivery. |
message.delivered |
The recipient's mail server confirmed successful delivery. |
message.bounced |
The message could not be delivered (hard or soft bounce). |
message.complained |
The recipient marked the message as spam via their mail client. |
Webhook Payload
Every webhook delivery is a POST request with a Content-Type: application/json body. The payload has a consistent envelope regardless of event type.
{
"id": "evt_01j9xkq7z8abc",
"type": "message.received",
"createdAt": "2026-04-16T10:23:45.000Z",
"webhookId": "wh_01j9abc123",
"data": {
"id": "msg_01j9xkp4n5def",
"inboxId": "inbox_01j8mq3r2t",
"threadId": "thr_01j9xkp4m1ghi",
"fromAddress": "alice@example.com",
"fromName": "Alice Chen",
"toAddresses": ["a1b2c3@agentsend.io"],
"subject": "Re: Your proposal",
"bodyText": "Looks great, let's move forward.",
"bodyHtml": "<p>Looks great, let's move forward.</p>",
"hasAttachments": false,
"receivedAt": "2026-04-16T10:23:44.812Z"
}
}Verifying Signatures
Every webhook request includes an X-AgentSend-Signature header. This is an HMAC-SHA256 hash of the raw request body, signed with your webhook's secret. Always verify this signature before processing a delivery to protect against forged requests.
import crypto from "node:crypto"; app.post("/hooks/agentsend", express.raw({ type: "application/json" }), (req, res) => { const sig = req.headers["x-agentsend-signature"]; const secret = process.env.AGENTSEND_WEBHOOK_SECRET; const expected = crypto .createHmac("sha256", secret) .update(req.body) // raw Buffer — do NOT parse JSON first .digest("hex"); if (!crypto.timingSafeEqual( Buffer.from(sig), Buffer.from(expected) )) { return res.status(401).send("Invalid signature"); } const event = JSON.parse(req.body); // handle event.type … res.sendStatus(200); });
import hmac, hashlib, os from flask import Flask, request, abort app = Flask(__name__) @app.route("/hooks/agentsend", methods=["POST"]) def webhook(): sig = request.headers.get("X-AgentSend-Signature", "") secret = os.environ["AGENTSEND_WEBHOOK_SECRET"].encode() expected = hmac.new(secret, request.data, hashlib.sha256).hexdigest() if not hmac.compare_digest(sig, expected): abort(401) event = request.get_json() # handle event["type"] … return "", 200
Always use a constant-time comparison (e.g. crypto.timingSafeEqual or hmac.compare_digest) when checking signatures to prevent timing attacks.
Retry Policy
AgentSend considers a delivery successful when your endpoint responds with any 2xx HTTP status code within 10 seconds. If the request times out or returns a non-2xx response, the delivery is retried with exponential back-off:
| Attempt | Delay after previous attempt |
|---|---|
| 1st retry | 5 seconds |
| 2nd retry | 30 seconds |
| 3rd retry | 5 minutes |
| 4th retry | 30 minutes |
| 5th retry | 2 hours |
After 5 failed retries (6 total attempts) the delivery is marked as failed and no further attempts are made. You can inspect failed deliveries in the delivery logs and manually replay them from the dashboard.
Respond with 200 OK as quickly as possible. If your processing logic takes time, accept the payload immediately and handle it asynchronously with a queue or background job.
Managing Webhooks
The webhooks API lets you list, update, and delete your registered endpoints.
List webhooks
Returns all webhooks registered on your account.
# GET /webhooks curl https://api.agentsend.io/webhooks \ -H "x-api-key: $AGENTSEND_API_KEY"
Update a webhook
Change the URL, description, or subscribed event types. You can also rotate the signing secret by passing "rotateSecret": true — the response will include a new secret.
# PUT /webhooks/{id} curl -X PUT https://api.agentsend.io/webhooks/wh_01j9abc123 \ -H "x-api-key: $AGENTSEND_API_KEY" \ -H "Content-Type: application/json" \ -d '{"events": ["message.received", "message.delivered", "message.bounced"]}'
Delete a webhook
Removes the webhook permanently. In-flight deliveries are still attempted before the deletion takes effect.
# DELETE /webhooks/{id} curl -X DELETE https://api.agentsend.io/webhooks/wh_01j9abc123 \ -H "x-api-key: $AGENTSEND_API_KEY"
Activity monitoring
Get a summary of recent event volume and error rates for a specific webhook.
# GET /webhooks/activity curl "https://api.agentsend.io/webhooks/activity?webhookId=wh_01j9abc123" \ -H "x-api-key: $AGENTSEND_API_KEY"
Delivery Logs
Every delivery attempt is recorded. Query the logs endpoint to inspect the full history for a webhook, including HTTP response codes, response bodies, and timing.
GET/webhooks/logs
| Query Parameter | Type | Description |
|---|---|---|
webhookId |
string | Filter logs to a specific webhook. |
status |
string | success, failed, or pending. |
limit |
number | Number of records to return (default 50, max 200). |
before |
string | Cursor for pagination; pass the id of the last record seen. |
# Fetch failed deliveries for a webhook curl "https://api.agentsend.io/webhooks/logs?webhookId=wh_01j9abc123&status=failed" \ -H "x-api-key: $AGENTSEND_API_KEY"
Each log entry includes:
- id — Unique delivery attempt ID.
- eventId — The originating event ID.
- eventType — e.g.
message.received. - status —
success,failed, orpending. - httpStatus — The HTTP response code returned by your endpoint.
- durationMs — Round-trip time in milliseconds.
- attempt — Which attempt this was (1 = first delivery).
- createdAt — Timestamp of the attempt.