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/deliveriesor 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.