Skip to content

SPREVA opens soon.

Join the waitlist and we'll email you once, when your account is ready.

No newsletter. Just one email when SPREVA opens.

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. 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. 2

    Copy the endpoint secret. It is shown only once, and it is what you check each request's signature with.

  3. 3

    Use Send test. It sends one webhook.test event, 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 as target.published.
  • x-spreva-event-id: the same as the body's id, 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.
JSON
{
  "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.

JavaScript
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);
}
Check it against the body exactly as it arrived, before any JSON parsing: parsing it and writing it out again changes the bytes, and the signature no longer matches.

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.