Webhooks
Receive a signed POST when a post is scheduled or finishes, verify the signature, handle retries and check the delivery history.
Instead of polling GET /v1/posts/:id in a loop, register a URL and Postmatic tells you.
Events
| Event | When |
|---|---|
post.scheduled | A schedule was accepted (POST /v1/posts with scheduledFor, or a scheduled retry). |
post.published | Every target published. |
post.partial | At least one target published and at least one failed. |
post.failed | Every target failed (including a missed schedule, MISSED). |
A scheduled post emits post.scheduled and then one final event. An immediate post emits only the final one. Cancelling through the API emits nothing. A POST /v1/posts/:id/retry produces a new final event once it finishes.
Subscribing
curl -X POST https://api.uat.postmatic.dev/v1/notifications \
-H "x-access-key: pm_live_…" \
-H "content-type: application/json" \
-d '{ "eventTypes": ["post.published", "post.partial", "post.failed"], "webhook": { "url": "https://your-app.com/hooks/postmatic" } }'The response carries webhook.secret (whsec_…) once. Save it. profileId restricts to a profile's posts; onlyScheduledPosts: true sends final events only for posts that were scheduled.
Test the destination before or after saving it with POST /v1/notifications/test ({ "notificationId": "ntf_…" } or { "webhook": { "url": "…" } }).
Verifying the signature
Every delivery is a POST with Content-Type: application/json and these headers:
| Header | Value |
|---|---|
X-Postmatic-Event-Id | The event's id (evt_…). Use it as the deduplication key. |
X-Postmatic-Event-Type | E.g. post.published. |
X-Postmatic-Timestamp | Unix timestamp in seconds. |
X-Postmatic-Signature | sha256= + HMAC-SHA256 hex of <timestamp>.<raw body>. |
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(secret: string, headers: Record<string, string>, rawBody: string): boolean {
const ts = headers['x-postmatic-timestamp'];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // 5 min tolerance
const expected = `sha256=${createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex')}`;
const given = headers['x-postmatic-signature'] ?? '';
return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}Sign over the raw body (do not re-serialize the JSON). Answer 2xx in under 10 seconds and process afterward.
Payload
{
"version": "2026-09-01",
"id": "evt_…",
"type": "post.published",
"createdAt": "2026-09-09T12:00:00.000Z",
"projectId": "proj_…",
"profileId": null,
"data": {
"post": {
"id": "post_…", "status": "published", "content": "Now live.",
"scheduledFor": null, "publishedAt": "2026-09-09T12:00:00.000Z",
"mediaItems": [{ "type": "image", "url": "https://…/photo.jpg" }],
"links": { "api": "https://api.uat.postmatic.dev/v1/posts/post_…" }
},
"platforms": [{
"platform": "instagram", "integrationId": "int_…", "profileId": null,
"status": "published", "platformPostId": "1790…", "platformPostUrl": "https://www.instagram.com/p/…/",
"publishedAt": "2026-09-09T12:00:00.000Z", "errorCode": null, "errorMessage": null, "warningMessage": null
}]
}
}Retries
Without a 2xx in 10 seconds, Postmatic retries after 30 s and after 2 min (3 attempts total), with the same body and the same id. After that the delivery stays failed in the history. Duplicates are possible: deduplicate by id.
History
GET /v1/notifications/:id/deliveries lists every event delivered to the subscription, with status (pending, delivered, failed), attempt, responseStatus and error.