Matrix: M30M33
Open in panel — /app/sending
Configuration

Webhooks

PostMTA delivers real-time event notifications to your endpoint via signed HTTP POST requests. This page covers webhook registration, event types, HMAC-SHA256 verification, idempotency, retry policy, and the dead letter queue.

Webhook Registration

Register a webhook endpoint with POST /v1/webhooks. You can register up to 20 active webhooks per workspace.

{webhook_register}

Request fields

FieldTypeRequiredDescription
urlstringYesHTTPS endpoint to receive events. Must return 2xx within 30 seconds.
eventsarray of stringsYesEvent types to subscribe to (see below).
secretstringYes32+ byte secret used for HMAC-SHA256 signature generation.
descriptionstringNoHuman-readable label for this webhook.
activebooleanNoEnable/disable without deleting. Defaults to true.

Listing Webhooks

{webhook_list}

Event Types

Subscribe to any combination of the following event types:

EventFired when...Deduplication window
deliveredThe remote MTA accepts the message (2xx SMTP response).72 hours
bouncedThe remote MTA rejects the message (4xx/5xx or timeout after 30 days).72 hours
complainedThe recipient marks the message as spam (via FBL / Abuse Feedback Loop).7 days
deferredThe remote MTA returns a soft fail; PostMTA will retry automatically.72 hours
rejectedPostMTA rejects the message before any SMTP delivery attempt (policy, suppression, validation).72 hours

Payload Structure

Every webhook POST contains a JSON body with these top-level fields:

{event_payload}

All timestamps are ISO 8601 UTC. The event_id field is stable and can be used for idempotency checks.

HMAC-SHA256 Signature Verification

Every webhook request includes a PostMTA-Signature header (also sent as X-PostMTA-Signature for compatibility). The value is sha256=<hex_digest>.

The digest is computed over the raw, unparsed request body as UTF-8. Do NOT use req.body after any JSON parsing middleware — read the raw bytes directly.

Important: Always verify the signature BEFORE processing the event. Accept requests only when timingSafeEqual returns true. Reject everything else with 401 Unauthorized.
{hmac_node}

Idempotency via event_id

Each event carries a stable event_id (prefixed evt_). If your endpoint receives the same event_id more than once (possible due to retry behaviour), treat it as a duplicate. Store processed event_id values in a set or database row and skip re-processing.

-- Example: idempotency check in SQL
SELECT 1 FROM processed_events WHERE event_id = $1 AND processed_at > NOW() - INTERVAL '72 hours';
-- If row exists, skip processing and return 200 OK immediately.

Retry Policy

If your endpoint returns a non-2xx response or times out (30-second connection timeout), PostMTA retries delivery with exponential backoff. The total retry window is approximately 72 hours.

AttemptDelay after previous failureCumulative elapsed
Attempt 1Immediate0 minutes
Attempt 215 minutes15 minutes
Attempt 31 hour1 hour 15 min
Attempt 44 hours5 hours 15 min
Attempt 516 hours~21 hours 15 min
Attempt 648 hours~69 hours 15 min
Timeout behaviour: If your endpoint does not respond within 30 seconds of the POST request, it is treated as a failure and retried. Design your endpoint to acknowledge receipt quickly (return 200 OK immediately) and process the event asynchronously.

Dead Letter Queue

After all 6 retry attempts are exhausted without a successful delivery (2xx response), the event is moved to the Dead Letter Queue (DLQ). DLQ events are stored for 30 days and can be:

curl -X POST https://api.postmta.com/v1/webhooks/whk_01HXTST001/dlq/evt_01HXEVT001/replay \\
  -H "Authorization: Bearer ***"

TLS Requirement (PMH-SEC-018)

Webhook endpoints must use HTTPS with TLS 1.2 or higher. Self-signed certificates are not accepted. Certificate chain validation is performed at connection time. Plain HTTP endpoints are rejected at registration time.

PMH-SEC-018: Webhook URLs using http:// (plaintext) or invalid TLS will be rejected during registration. Update any existing plaintext webhook URLs immediately to avoid event loss.

Testing Webhooks

Send a test payload to your endpoint without triggering real events:

{webhook_test}

This delivers a synthetic test event to your endpoint. Use it to validate connectivity, TLS, and signature verification in staging environments.

Webhook Rate Limits

PostMTA does not apply rate limits to your webhook endpoint — the responsibility is on you to respond within 30 seconds. If your endpoint consistently times out or returns 5xx errors, PostMTA will continue retrying up to the maximum attempts. Consider scaling your webhook consumer horizontally to handle burst deliveries.