MailLandDocs

Webhooks Overview

Get real-time notifications about email events via webhooks

Webhooks deliver real-time HTTP notifications to your server when email events occur, deliveries, bounces, complaints, and more. Instead of polling for status changes, MailLand pushes events to your endpoint as they happen.

How Webhooks Work

Configure a webhook

Register your endpoint URL and select which events you want to receive.

Mail.Land sends events

When an event occurs (e.g., an email is delivered), MailLand sends a signed HTTP POST to your endpoint with the event payload.

Your server responds

Return a 2xx status code to acknowledge receipt. Non-2xx responses trigger automatic retries.

Create a Webhook

POST https://api.mail.land/v1/webhooks
Create a webhook
curl -X POST https://api.mail.land/v1/webhooks \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production Events",
    "webhookUrl": "https://your-app.com/webhooks/mailland",
    "products": ["transactional", "marketing"],
    "events": ["message.delivered", "message.bounced", "message.deferred", "message.complained"],
    "includeFutureEvents": true,
    "scope": "all_domains"
  }'

Request Fields

FieldTypeRequiredDescription
namestringNoHuman-readable name for this webhook
webhookUrlstringYesHTTPS endpoint URL to receive events
productsarrayNoProduct filter: transactional, marketing, inbound, system
eventsarrayNoSpecific events to subscribe to (see Events)
includeFutureEventsbooleanNoReceive new events added to subscribed products (default: true)
scopestringNoall_domains (default) or selected_domains
domainIdsarrayNoDomain IDs to filter by (required if scope is selected_domains)

Response

Status: 201 Created

Webhook created response
{
  "webhook": {
    "id": "k3m9x2p7qw4rtz",
    "name": "Production Events",
    "webhookUrl": "https://your-app.com/webhooks/mailland",
    "products": ["transactional", "marketing"],
    "events": ["message.delivered", "message.bounced", "message.deferred", "message.complained"],
    "includeFutureEvents": true,
    "scope": "all_domains",
    "domainIds": [],
    "isActive": true,
    "createdAt": "2026-02-17T10:00:00Z",
    "successCount": 0,
    "failureCount": 0,
    "consecutiveFailures": 0,
    "pauseThreshold": 10
  },
  "secret": "whsec_a1b2c3d4e5f6g7h8i9j0..."
}

The secret is only shown once when the webhook is created. Store it securely: you'll need it to verify webhook signatures. If you lose it, you can regenerate it, which invalidates the old secret.

Webhook URLs must use HTTPS. MailLand validates URLs against SSRF attacks: private network addresses (127.0.0.1, 10.x, 192.168.x, etc.) are rejected. Delivery is to HTTPS on port 443 only; a URL on another port, or one that points at MailLand's own servers or host names, is refused with 400 WEBHOOK_DESTINATION_NOT_ALLOWED. A different TLS port needs an explicit approval from MailLand support.

List Webhooks

GET https://api.mail.land/v1/webhooks
List webhooks
curl -X GET https://api.mail.land/v1/webhooks \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
List response
[
  {
    "id": "k3m9x2p7qw4rtz",
    "name": "Production Events",
    "webhookUrl": "https://your-app.com/webhooks/mailland",
    "products": ["transactional", "marketing"],
    "events": ["message.delivered", "message.bounced"],
    "isActive": true,
    "lastTriggeredAt": "2026-02-17T12:30:00Z",
    "lastSuccessAt": "2026-02-17T12:30:00Z",
    "successCount": 1542,
    "failureCount": 3,
    "consecutiveFailures": 0,
    "createdAt": "2026-02-10T08:00:00Z"
  }
]

Returns up to 100 webhooks ordered by creation date (newest first).

Get Webhook Detail

GET https://api.mail.land/v1/webhooks/{id}
Get webhook detail
curl -X GET https://api.mail.land/v1/webhooks/k3m9x2p7qw4rtz \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Returns the full webhook configuration plus the 50 most recent delivery logs. See Retry Logic for delivery log details.

Update a Webhook

PUT https://api.mail.land/v1/webhooks/{id}
Update webhook
curl -X PUT https://api.mail.land/v1/webhooks/k3m9x2p7qw4rtz \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "All Events",
    "events": ["message.delivered", "message.bounced", "message.deferred", "message.complained"],
    "isActive": true
  }'

All fields are optional: only include the fields you want to change.

Test a Webhook

Send a test event to verify your endpoint is working:

POST https://api.mail.land/v1/webhooks/{id}/test
Test a webhook
curl -X POST https://api.mail.land/v1/webhooks/k3m9x2p7qw4rtz/test \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Test result
{
  "success": true,
  "statusCode": 200,
  "statusText": "OK",
  "durationMs": 145,
  "responseBody": "{\"received\": true}",
  "headersSent": {
    "X-MLND-Signature": "t=1708171200,v1=abc123...",
    "X-MLND-Event-Id": "evt_test_abc123",
    "X-MLND-Event-Version": "1",
    "X-MLND-Event": "webhook.test",
    "X-MLND-Webhook-Id": "k3m9x2p7qw4rtz",
    "X-MLND-Attempt": "1"
  },
  "wouldReceiveRealEvents": true,
  "liveEvents": ["message.delivered", "message.bounced"],
  "dormantEvents": {},
  "subscriptionWarning": null
}

The test send uses the same signing and the same delivery transport as a real event, so a success here proves your URL, your TLS and your signature verification.

It does not prove your subscription: a test is sent on demand, not routed by your event selection. The last four fields answer that separately:

FieldMeaning
wouldReceiveRealEventsWhether any real event can reach this endpoint at all
liveEventsSubscribed events that pass the same filter the dispatcher applies
dormantEventsSubscribed events that will never arrive, each with the reason
subscriptionWarningSet when the endpoint is reachable but permanently idle

A "success": true alongside "wouldReceiveRealEvents": false is the important case: the endpoint works and is subscribed to nothing that can be delivered.

If the test fails, the errorType field indicates the reason: ssrf_blocked (the URL, or a redirect from it, pointed somewhere the platform refuses to send to) or network_error (timeout, TLS failure, connection refused, errorMessage carries the detail).

Regenerate Secret

If you lose or need to rotate your webhook secret:

POST https://api.mail.land/v1/webhooks/{id}/regenerate-secret
Regenerate webhook secret
curl -X POST https://api.mail.land/v1/webhooks/k3m9x2p7qw4rtz/regenerate-secret \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
New secret
{
  "secretKey": "whsec_x9y8z7w6v5u4..."
}

Regenerating the secret immediately invalidates the old one. Update your server's verification code before the next webhook delivery, or you'll reject valid events.

Domain Scope

By default, webhooks receive events for all domains in your workspace. To limit a webhook to specific domains:

Domain-scoped webhook
{
  "webhookUrl": "https://your-app.com/webhooks/mailland",
  "scope": "selected_domains",
  "domainIds": ["d4n8q1zt5wm3xc", "b7r2mv9k0jy4pw"]
}

Only events related to those domains will trigger the webhook.

Auto-Pause on Failures

MailLand automatically pauses webhooks after 10 consecutive failed delivery attempts. This prevents wasting resources on endpoints that are down. The counter is of attempts, not of events, one event that exhausts its retry ladder contributes several failures, and any successful delivery resets it to zero.

When a webhook is paused:

  • isActive becomes false
  • The pausedAt timestamp is set
  • The pausedReason describes why: the failure count and the last error
  • An in-app notification is created

Resume a Paused Webhook

After fixing the issue, resume delivery:

POST https://api.mail.land/v1/webhooks/{id}/resume
Resume a paused webhook
curl -X POST https://api.mail.land/v1/webhooks/k3m9x2p7qw4rtz/resume \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

This clears the pause state, resets the consecutive failure counter, and reactivates the webhook (isActive becomes true again), so future events are delivered to it normally.

Delete a Webhook

DELETE https://api.mail.land/v1/webhooks/{id}
Delete a webhook
curl -X DELETE https://api.mail.land/v1/webhooks/k3m9x2p7qw4rtz \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Returns 204 No Content. If the webhook is used by inbound email routes, those routes will be updated or disabled automatically.

Error Responses

StatusCodeDescription
400INVALID_REQUESTInvalid URL, events, or scope configuration
403PLAN_LIMIT_REACHEDMaximum webhooks for your plan reached
404(empty body)Webhook ID does not exist: returned by get, update, delete, regenerate-secret and resume
404RESOURCE_NOT_FOUNDWebhook ID does not exist, returned by /test only

Next Steps

On this page