Webhooks

2 min read

Notify another system when a Sofia post goes live or fails: signed webhooks registered with the API, with retries.

A webhook is an HTTP call that Sofia triggers when a post goes live or fails. Use it to log content in a spreadsheet, notify a messaging tool, or start a scenario in Zapier or n8n.

On this pageAdding oneBuilding the receiverWhen a webhook stops firingSee also

Adding one

  • 1. Give it an address. A public https address where Sofia will send the event.
  • 2. Choose the events. post.published and post.failed.
  • 3. Register it. POST /v1/webhooks: the signing secret (whsec_…) is shown only in this response.
  • 4. Send a test. POST /v1/webhooks/{id}/test sends a webhook.test event, signed like the others.

The full request is detailed in the API tab: Register a webhook. Only posts created by your application are announced.

Building the receiver

Always send a test first and keep the body you receive: it is the contract, more reliable than any description. 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.

  • Verify the signature. Sofia-Signature is t=<unix time>,v1=<hex>, where v1 is the HMAC-SHA256 of <t>.<raw body> with your secret. Compute it on the raw body and reject an old timestamp.
  • Reply `2xx` fast, then do the work afterwards: a slow endpoint is indistinguishable from a broken one.
  • Be idempotent. The same event can arrive twice: deduplicate on Sofia-Event-Id.

When a webhook stops firing

The address is not reachable

Sofia calls from its servers: the address must be a public https one. A redirect is never followed, and 10 seconds without a response count as a failure.

The address returns an error

Any response other than 2xx triggers a new attempt after 1 minute, 5 minutes, 30 minutes and 2 hours, up to 5 attempts. Look at your own logs first, then reproduce with POST /v1/webhooks/{id}/test.

The post was not created by your application

A webhook only announces posts created by your application; a post created in the interface does not trigger it.