Documentation
Sending

Webhooks

Receive real-time notifications about delivery events and replies.

Overview

Configure a webhook URL in your Dashboard to receive HTTP POST requests whenever a delivery event occurs. LDM sends a JSON payload to your endpoint with event details.


Events

EventDescription
delivery.confirmedMessage confirmed in recipient's inbox.
delivery.failedMessage bounced or delivery failed.
reply.receivedRecipient replied to your message.
creative.approvedCreative passed moderation.
creative.rejectedCreative failed moderation.

Payload examples

delivery.confirmed

{
  "event": "delivery.confirmed",
  "message_id": "msg_01HQX7kP...",
  "to": "cto@acme.com",
  "status": "inbox",
  "delivered_at": "2026-04-16T09:00:12Z",
  "cost": 0.015
}

delivery.failed

{
  "event": "delivery.failed",
  "message_id": "msg_01HQX7kP...",
  "to": "nobody@invalid.test",
  "status": "bounce",
  "reason": "mailbox_not_found",
  "failed_at": "2026-04-16T09:01:00Z"
}

reply.received

{
  "event": "reply.received",
  "message_id": "msg_01HQX7kP...",
  "from": "cto@acme.com",
  "subject": "Re: Quick question about your infrastructure",
  "body": "Thanks for reaching out! Let's schedule a call.",
  "received_at": "2026-04-16T10:30:00Z"
}

creative.approved

{
  "event": "creative.approved",
  "creative_id": "cr_9f2a...",
  "approved_at": "2026-04-16T08:55:00Z"
}

creative.rejected

{
  "event": "creative.rejected",
  "creative_id": "cr_9f2a...",
  "reason": "promotional_content",
  "details": "Message contains unsolicited promotional content.",
  "rejected_at": "2026-04-16T08:55:00Z"
}

Signature verification

Every webhook request includes an X-LDM-Signature header containing an HMAC-SHA256 signature of the request body, signed with your webhook secret. Always verify this signature to ensure the request is from LDM.

Node.js verification example

import crypto from 'crypto';

function verifyWebhook(body: string, signature: string, secret: string): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');

  const sig = signature.replace('sha256=', '');

  return crypto.timingSafeEqual(
    Buffer.from(sig, 'hex'),
    Buffer.from(expected, 'hex')
  );
}

// In your webhook handler:
app.post('/webhooks/ldm', (req, res) => {
  const signature = req.headers['x-ldm-signature'] as string;
  const isValid = verifyWebhook(
    JSON.stringify(req.body),
    signature,
    process.env.LDM_WEBHOOK_SECRET!
  );

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process the event
  const { event, message_id } = req.body;
  console.log(`Received ${event} for ${message_id}`);

  res.status(200).json({ received: true });
});

Retry policy

If your endpoint returns a non-2xx status code or times out, LDM retries the webhook with exponential backoff:

  • Retry 1 — after 1 minute
  • Retry 2 — after 5 minutes
  • Retry 3 — after 30 minutes

After 3 failed attempts, the event is dropped. You can view missed events in the Dashboard webhook logs.