Webhooks
Register a webhook
Registers an https URL that Sofia calls with a signed JSON event, retried up to five times on failure. The secret is shown once; 10 webhooks at most.
Register a webhook
Register a public https address and the events you want. The signing secret (whsec_…) is shown only in this response. An application can have up to 10 webhooks.
curl -X POST https://api.sofia-post.com/api/v1/webhooks \
-H "Authorization: Bearer $SOFIA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"url":"https://app.example.com/hooks/sofia","events":["post.published","post.failed"]}'Events
| Event | data |
|---|---|
post.published | postId, establishmentId, platform, publishedAt, permalink |
post.failed | postId, establishmentId, platform, error |
webhook.test | webhookId |
Sofia sends each event as a POST in JSON format { id, type, createdAt, data }, with the Sofia-Event-Id, Sofia-Event-Type and Sofia-Signature headers. Only posts created by your application are announced.
Verify the signature
Sofia-Signature is t=<unix time>,v1=<hex>, where v1 is the HMAC-SHA256 of <t>.<raw body> with your secret. Verify it on the raw body, and reject an old timestamp:
import { createHmac, timingSafeEqual } from 'node:crypto';
const isSignedBySofia = (header, rawBody, secret) => {
const parts = Object.fromEntries(
(header ?? '').split(',').map((part) => part.trim().split('=', 2)),
);
const timestamp = Number(parts.t);
if (!Number.isInteger(timestamp) || typeof parts.v1 !== 'string') return false;
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest();
const received = Buffer.from(parts.v1, 'hex');
return received.length === expected.length && timingSafeEqual(received, expected);
};Delivery and retries
- A
2xxresponse delivers the event. Any other response, a redirect (never followed) or 10 seconds without a response triggers a new attempt after 1 minute, 5 minutes, 30 minutes and 2 hours, up to 5 attempts. - The same event can arrive twice: deduplicate on
Sofia-Event-Id. - Reply fast and process the event afterwards.
Authorizations
AuthorizationstringheaderrequiredYour personal key or OAuth access token, sent as
Bearer <token>.
Body
urlstringrequiredThe https address Sofia calls. A host that resolves to a private, loopback or link-local address is refused
eventsstring[]requiredThe events to receive
Response
The webhook and its signing secret, shown only once.
idstringrequiredIdentifier of the webhook
urlstringrequiredAddress called by Sofia
eventsstring[]requiredThe subscribed events
createdAtstring<date-time>requiredWhen the webhook was registered
secretstringrequiredThe HMAC-SHA256 key that signs each delivery (
Sofia-Signature). Shown this once only