Webhooks
Get real-time notifications for email and inbox events via HTTPS webhooks.
Last updated 2026-03-29
Instead of polling, you can register a webhook to receive real-time HTTP POST notifications when events occur in your account.
Creating a Webhook#
const webhook = await lm.createWebhook({
url: 'https://your-agent.example.com/hooks/lobstermail',
events: ['email.received', 'email.sent', 'email.bounced'],
});
// IMPORTANT: Store the secret — it is only returned once
console.log(webhook.id); // wh_abc123
console.log(webhook.secret); // whsec_...
Or via the API:
POST /v1/webhooks
{
"url": "https://your-agent.example.com/hooks/lobstermail",
"events": ["email.received", "email.sent"]
}
The url must use HTTPS. The secret is returned only on creation — store it securely.
Events#
| Event | Description |
|---|---|
email.received | A new inbound email was delivered to one of your inboxes. |
email.sent | An outbound email was successfully sent via SES. |
email.bounced | An outbound email bounced (hard or soft bounce). |
email.quarantined | An inbound email was flagged as spam or phishing and quarantined. |
email.scan.complete | Security scanning finished for an email (injection, spam, phishing scores available). |
email.thread.new | A new conversation thread was created (first email in a thread). |
email.thread.reply | A reply was added to an existing conversation thread. |
inbox.created | A new inbox was created. |
inbox.expired | An inbox was deactivated due to TTL expiry. |
You can subscribe to any combination of events when creating a webhook.
Webhook Payload#
All events share the same envelope structure:
{
"event": "email.received",
"timestamp": "2026-02-17T12:00:00Z",
"data": { ... }
}
The data shape depends on the event type.
Email Events#
email.received, email.quarantined, email.scan.complete, email.thread.new, and email.thread.reply include email summary data:
{
"event": "email.received",
"timestamp": "2026-02-17T12:00:00Z",
"data": {
"emailId": "eml_abc123",
"inboxId": "ibx_xyz789",
"direction": "inbound",
"from": "sender@example.com",
"to": ["recipient@lobstermail.ai"],
"subject": "Hello",
"preview": "First 200 characters of the email...",
"threadId": "thd_def456",
"security": {
"injectionRiskScore": 0.0,
"flags": []
},
"receivedAt": "2026-02-17T12:00:00Z"
}
}
email.sent#
{
"event": "email.sent",
"timestamp": "2026-02-17T12:00:00Z",
"data": {
"emailId": "eml_abc123",
"inboxId": "ibx_xyz789",
"from": "you@lobstermail.ai",
"to": ["recipient@example.com"],
"subject": "Re: Hello",
"messageId": "<ses-message-id>"
}
}
email.bounced#
{
"event": "email.bounced",
"timestamp": "2026-02-17T12:00:00Z",
"data": {
"emailId": "eml_abc123",
"inboxId": "ibx_xyz789",
"recipientAddress": "bad@example.com",
"bounceType": "Permanent"
}
}
inbox.created#
{
"event": "inbox.created",
"timestamp": "2026-02-17T12:00:00Z",
"data": {
"inboxId": "ibx_xyz789",
"address": "sarah-shield@lobstermail.ai",
"localPart": "sarah-shield",
"domain": "lobstermail.ai",
"displayName": "Sarah Shield",
"createdAt": "2026-02-17T12:00:00Z",
"expiresAt": null
}
}
inbox.expired#
{
"event": "inbox.expired",
"timestamp": "2026-02-17T12:00:00Z",
"data": {
"inboxId": "ibx_xyz789",
"expiredAt": "2026-02-17T12:00:00Z"
}
}
Scoping Webhooks#
By default, a webhook fires for events across all inboxes in your account. To scope it to a specific inbox, pass inboxId:
const webhook = await lm.createWebhook({
url: 'https://your-agent.example.com/hooks/lobstermail',
events: ['email.received'],
inboxId: 'ibx_xyz789', // Only fires for this inbox
});
Verifying Signatures#
Every webhook request includes an X-LobsterMail-Signature header containing an HMAC-SHA256 signature of the raw request body, prefixed with sha256=. Always verify this before processing.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyWebhook(body: string, signatureHeader: string, secret: string): boolean {
const expected = `sha256=${createHmac('sha256', secret).update(body).digest('hex')}`;
if (expected.length !== signatureHeader.length) return false;
return timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}
// In your HTTP handler:
const isValid = verifyWebhook(rawBody, req.headers['x-lobstermail-signature'], webhookSecret);
if (!isValid) {
return new Response('Invalid signature', { status: 401 });
}
WebSocket Events#
All the same events are also published via WebSocket in real-time. See the WebSocket guide for details on subscribing.
Failure and Retry Behavior#
LobsterMail retries failed deliveries with exponential backoff. If your endpoint returns a non-2xx status code (or times out after 10 seconds), the delivery is retried up to 5 times with exponential backoff starting at 30 seconds.
After 10 consecutive failures across all deliveries, the webhook is automatically disabled. To re-enable a disabled webhook, delete it and create a new one.
Managing Webhooks#
// List all webhooks
const webhooks = await lm.listWebhooks();
// Delete a webhook
await lm.deleteWebhook('wh_abc123');