Reference
Webhooks
Get a signed request on your own server when a post is published or fails, a file is ready, or a social account needs attention.
Add an endpoint
- 1
In SPREVA, open Developer, then Webhooks, add your server's address and choose the events it should hear about. The address must be public and answer with a 2xx status within 10 seconds.
- 2
Copy the endpoint secret. It is shown only once, and it is what you check each request's signature with.
- 3
Use Send test. It sends one
webhook.testevent, signed like the real ones, and Recent deliveries shows the status your server answered and how long it took.
An endpoint can also be added through the REST API, with a key that has webhooks:write.
What each request carries
A POST with a JSON body and four headers:
x-spreva-event: the event, such astarget.published.x-spreva-event-id: the same as the body'sid, and the same on every retry and replay.x-spreva-timestamp: when the request was signed, in Unix seconds.x-spreva-signature:v1=followed by the signature.
{
"id": "7f3c2d10-0000-4000-8000-000000000000",
"type": "target.published",
"createdAt": "2026-10-01T09:00:02.412Z",
"workspaceId": "0b9f6c1e-0000-4000-8000-000000000001",
"data": {
"type": "TargetPublished",
"workspaceId": "0b9f6c1e-0000-4000-8000-000000000001",
"postId": "5d2a7c3e-0000-4000-8000-000000000002",
"targetId": "9a1f4b7d-0000-4000-8000-000000000003",
"provider": "instagram",
"remoteId": "17900000000000000",
"remoteUrl": "https://www.instagram.com/p/EXAMPLE/"
}
}data is the event itself, and data.type is its name inside SPREVA. Use the ids in data to fetch the full object through the REST API.
Events
An endpoint receives the events it chose. Send test reaches it whatever it chose.
Posts
post.created- A post was created
post.scheduled- A post was given a date
post.published- A post went out to every account
post.partial- A post ended with mixed results across its accounts
post.failed- A post failed on every account
Publications (one per account)
target.published- One account published the post
target.failed- One account could not publish
target.awaiting_user- An account needs you to finish publishing in its own app
Media
media.ready- An uploaded file is ready to use
media.failed- A file could not be processed or generated
Social accounts
connection.expired- A social account needs reconnecting
connection.revoked- A social account withdrew its permission
Analytics
analytics.updated- New numbers arrived for a published post
Check the signature
The signature is an HMAC-SHA256 of the timestamp, a dot and the raw body, keyed with the endpoint secret. Reject any request whose signature does not match.
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the request body as a string, before any JSON parsing
export function isFromSpreva(rawBody, headers, secret) {
const signed = headers["x-spreva-timestamp"] + "." + rawBody;
const expected = Buffer.from("v1=" + createHmac("sha256", secret).update(signed).digest("hex"));
const received = Buffer.from(headers["x-spreva-signature"] ?? "");
return received.length === expected.length && timingSafeEqual(received, expected);
}Retries and replays
- Answer with a 2xx status within 10 seconds. Anything else, or no answer, is retried up to 5 times: after 30 seconds, 2 minutes, 10 minutes, 1 hour and 6 hours.
- Redirects are not followed, so use the final address of your endpoint.
- A retry or a replay carries the same
x-spreva-event-id: use it to ignore an event you already handled. - Replay delivery, in Recent deliveries, sends an event again, with its retries if your server still fails, so you can fix your endpoint without publishing twice.