Help · for developers

Sending events to your own software.

For people who write code. Everyone else wants Slack or email.

Back to the help center.

What this is

Register an https endpoint and SupahPost will POST a small JSON body to it whenever one of eight things happens. It is the push half of the API — MCP and the REST endpoints are pull.

The developers row expanded, showing an example JSON body carrying only identifiers
The body carries identifiers and nothing else.

Eight events, and the list is closed:

  • post.published, post.failed, post.scheduled
  • post.approval_requested, post.approved, post.changes_requested
  • account.needs_reconnect
  • report.generated

It is included on every plan.

The payload carries identifiers, never content

Every value in the body is an id-shaped scalar: an event name, an event id, a timestamp, a post id, a platform, an account id. There is no caption, no image, no analytics figure and no credential in any event, ever.

The one thing you can add is your own: if you have linked a post, a report or a brand to a record in your own system, the post and report events carry that record's id back to you, as links, brand and brand_links. They are ids too, and held to the same rule.

This is enforced structurally rather than by a list of forbidden fields — a value must have no spaces in it, so no sentence this product holds can pass under any key. If you need the caption, fetch it over MCP or the API with a credential of your own. The upside is that the delivery log in Settings can show you the exact body of every send with nothing redacted.

Verifying a delivery came from us

Every request carries an HMAC-SHA256 signature of the raw body, keyed on a secret shown to you exactly once when you create the endpoint. Compute the same HMAC over the bytes you received and compare in constant time.

Two things about the header are worth knowing before you write the check:

  • It can contain more than one v1= value. After a rotation the previous secret keeps signing for 24 hours, as a second value in the same header. Accept the request if any v1= matches and rotation costs you no downtime and no code change.
  • Sign the raw bytes, not a re-serialised object. Parsing and re-encoding JSON changes whitespace and key order, and the signature will never match.

Rotate the secret from the endpoint's row. The button is safe to press: the old secret keeps working for a day.

Retries, and when we give up

A delivery that fails is retried seven times on a widening schedule — 30 seconds, then roughly doubling, out to 12 hours — and abandoned 24 hours after the event.

A 4xx is not retried. A 404 or a 401 is your server telling us the address is wrong, and repeating the request will not make it right. Anything else — a 5xx, a timeout, a connection refused — is treated as temporary and retried.

After ten final failures in a row we disable the endpoint, once, and tell you in the app and by email. Re-enable it from its row when the receiver is back.

Addresses we will not post to

Only https, and only to a publicly routable address. Loopback, private ranges and link-local addresses are refused — when you save the endpoint and again at send time, because DNS can change in between.

Redirects are not followed. Re-issuing a signed request at an address a response named would be an exfiltration primitive with our signature on it. Point the endpoint at its final destination.

Testing against a receiver on your laptop therefore does not work from our servers. Use a tunnelling service that gives you a public https address, or run SupahPost locally.

The delivery log

Each endpoint has a log showing status, response code, latency, retry count and the exact body sent. "Did you send it, and what did my server say" is how every integration like this gets debugged, so it is all on one row rather than behind a click.