post mate · docs

Webhooks

HMAC-signed outbound events, exponential-backoff retries, daily delivery log. Wire Post Mate into your own pipelines.

Webhooks let you subscribe to events on your Post Mate workspace — when a post publishes, when it fails, when a new account is connected — without polling our API.

Manage webhooks at /app/settings/api under the Webhooks tab.

How it works

You give us:

  • A target URL (HTTPS, must respond within 10 seconds)
  • A list of event types to subscribe to
  • An optional secret for HMAC-SHA256 signing

We send a POST to that URL when matching events fire. The body is JSON; the headers include signature + timestamp for verification.

Event types

EventFires when
post.createdA post is created (in any status)
post.scheduledA post moves to status scheduled
post.publishedA post successfully publishes to a network
post.failedA post fails to publish after retries
post.cancelledA scheduled post is cancelled
account.connectedA new social account is connected
account.disconnectedA social account is disconnected

A single post fanning out to four networks generates four post.published events (or a mix of published / failed), each with its own platform identifier.

Payload shape

{
  "id": "evt_01HSXJ…",
  "type": "post.published",
  "workspace_id": "ws_01HSXG…",
  "created_at": "2026-06-01T09:00:03.124Z",
  "data": {
    "post_id": "post_01HSXF…",
    "platform": "instagram",
    "external_id": "17912345678901234",
    "external_url": "https://www.instagram.com/p/abc123/",
    "scheduled_at": "2026-06-01T09:00:00Z"
  }
}

Signing

If you set a secret, every webhook request includes:

X-PostMate-Timestamp: 1717228803
X-PostMate-Signature: sha256=2af1b34cf5...

To verify:

import crypto from 'node:crypto';

function verify(rawBody: string, headers: Headers, secret: string): boolean {
  const timestamp = headers.get('x-postmate-timestamp')!;
  const signature = headers.get('x-postmate-signature')!.replace('sha256=', '');
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');
  // Use timingSafeEqual to avoid timing-attack leaks.
  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(signature, 'hex'),
  );
}

Reject requests where the timestamp is more than 5 minutes old — that's the replay-protection window.

Retries

Failed deliveries (non-2xx response, timeout, network error) retry with exponential backoff:

  • Attempt 1: immediate
  • Attempt 2: after 1 minute
  • Attempt 3: after 5 minutes
  • Attempt 4: after 25 minutes
  • Attempt 5: after 2 hours

After 5 failed attempts, we stop retrying and mark the delivery as permanently failed. The event is still visible in the delivery log so you can manually re-fire if needed.

Delivery log

Every webhook send shows up in your dashboard with:

  • Event ID and type
  • Target URL
  • HTTP status returned
  • Response body (first 500 chars)
  • Duration
  • Next retry time (if pending)

The log keeps 30 days of history.

Local development

For testing webhooks against localhost, use ngrok or Cloudflare Tunnel to expose your local server. Then point a Post Mate webhook at the tunnel URL. Both tools give you stable URLs that survive across sessions on their paid tiers.

Cost

Webhooks are included in the same $5/mo API add-on as the REST API and MCP. There's no per-event charge — you can subscribe to every event type and we'll deliver them.

On this page