Guides
Webhooks

Webhooks

Get notified when things happen in Pluto (a post publishes, a post fails, an account connects) instead of polling.

Subscribe

Create an endpoint with the API (or from the dashboard). Point it at an HTTPS URL you control and list the events you care about. Plain REST works today:

curl -X POST https://api.joinpluto.com/v1/webhooks \
  -H "Authorization: Bearer $PLUTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://your-app.com/hooks/pluto","events":["post.published","post.failed"]}'
# The signing secret is returned once on create — store it.

Once the TypeScript SDK (pluto-sdk-ts) publishes, the same call is:

// pluto-sdk-ts — publishing soon.
const hook = await pluto.webhooks.create({
  url: 'https://your-app.com/hooks/pluto',
  events: ['post.published', 'post.failed'],
});

Events

Common events include:

  • post.published: a scheduled post went live on a platform.
  • post.failed: a post attempt failed (bad token, rate limit, platform error).
  • post.scheduled: a post was accepted and queued.
  • account.connected: a social account finished connecting.

The complete, current list is in the API Reference ↗ under the webhook schema.

Verify the signature

Every delivery is signed. Pluto sends a Pluto-Signature header:

Pluto-Signature: sha256=<hex hmac>

The signature is HMAC-SHA256(rawRequestBody, yourEndpointSecret). Verify it on your side using the raw body (before JSON parsing) and a constant-time compare:

import { createHmac, timingSafeEqual } from 'node:crypto';
 
function verify(rawBody: string, header: string, secret: string): boolean {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(header);
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}

Reject any request whose signature doesn't match; that's how you know it's really from Pluto.

Delivery & retries

  • Deliveries retry with exponential backoff on failure.
  • After the retry budget is exhausted, the event moves to a dead-letter queue.
  • Inspect recent deliveries (status, response code) via GET /v1/webhooks/deliveries or the SDK:
const { deliveries } = await pluto.webhooks.listDeliveries({ limit: 50 });

Return a 2xx quickly from your endpoint; do heavy work asynchronously so a slow handler doesn't trip the retry logic.