> Discover all available Postdom documentation from the machine-readable index: https://postdom.com/llms.txt

Integration · outbound events

# Outbound webhooks

Receive publish outcomes and fresh performance evidence without polling. Every request is signed; delivery is at least once.

Current contract

## Events

`post.published`

### Publish succeeded

The post reached its terminal published state. Destination evidence is included.

`post.failed`

### Publish did not fully succeed

The post is failed or partial. Inspect the payload status and destination errors.

`performance.updated`

### New measurement stored

A new evidence-backed metric snapshot is available. Null values remain null.

Stable envelope

## Request

Postdom sends an HTTPS `POST` with the exact JSON body shown by the event. Use `Webhook-Id` as the idempotency key for your receiver.

_EXAMPLE WEBHOOK BODY_

```
{
  "id": "0e386be3-…",
  "type": "post.published",
  "created_at": "2026-08-28T04:12:00.000Z",
  "data": {
    "post_id": "53b81c96-…",
    "status": "published",
    "destinations": []
  }
}
```

### Headers

`Webhook-Id` identifies the event, `Webhook-Timestamp` is Unix seconds, and `Webhook-Signature` contains the `v1` HMAC.

HMAC-SHA256

## Verify before processing

Sign the raw request body—not parsed and re-serialized JSON—with `event_id.timestamp.body`. Compare signatures in constant time and reject stale timestamps.

_NODE.JS VERIFICATION SKELETON_

```
import { createHmac, timingSafeEqual } from "node:crypto";

const eventId = request.headers.get("Webhook-Id");
const timestamp = request.headers.get("Webhook-Timestamp");
const supplied = request.headers.get("Webhook-Signature");
const body = await request.text();

// Reject stale timestamps first; five minutes is a sensible replay window.
const expected = "v1=" + createHmac("sha256", process.env.POSTDOM_WEBHOOK_SECRET)
  .update(eventId + "." + timestamp + "." + body)
  .digest("hex");

const valid = supplied?.length === expected.length &&
  timingSafeEqual(Buffer.from(supplied), Buffer.from(expected));
```

Secrets are shown once when an endpoint is created or rotated. Store them in a secret manager. New deliveries use the rotated secret immediately; a request already in flight may still use the previous secret for up to 15 seconds.

At least once

## Delivery and retries

Any 2xx response completes a delivery. Redirects, timeouts, network failures, and other HTTP statuses are retried after approximately 1 minute, 5 minutes, 30 minutes, 2 hours, and 12 hours. The sixth failed attempt is exhausted.

### Make receivers idempotent

A worker may retry after an ambiguous network result. Persist `Webhook-Id` before applying the event so a duplicate delivery does not duplicate downstream work.

Pausing or removing an endpoint cancels queued retries. A request already in flight may still finish.

Authenticated dashboard

## Manage endpoints

Workspace owners, admins and members can add up to five HTTPS endpoints from Accounts → Developer webhooks, choose event types, pause delivery, rotate the secret, remove the endpoint, and inspect recent attempts.

Endpoint URLs must use public DNS and the default HTTPS port. Postdom rejects IP literals, private or reserved DNS answers, credentials, fragments, and redirects.

 VERIFIED · 26 AUGUST 2026  **Transactional event outbox · Worker delivery lease · Dashboard endpoint management**

Publish and performance events are inserted in the same database transaction as the state they describe.
