Launch-Free 3 months Builder plan-

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#

EventDescription
email.receivedA new inbound email was delivered to one of your inboxes.
email.sentAn outbound email was successfully sent via SES.
email.bouncedAn outbound email bounced (hard or soft bounce).
email.quarantinedAn inbound email was flagged as spam or phishing and quarantined.
email.scan.completeSecurity scanning finished for an email (injection, spam, phishing scores available).
email.thread.newA new conversation thread was created (first email in a thread).
email.thread.replyA reply was added to an existing conversation thread.
inbox.createdA new inbox was created.
inbox.expiredAn 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');