Webhooks

Receiving webhooks

Flux sends an HTTPS POST to your endpoint when something happens: an invoice is approved, a payment arrives, a customer changes. Respond quickly, verify the signature and process each event once.

Set up an endpoint#

Register your URL in the Flux app (Settings → Developers → Webhooks) or with POST /webhook-endpoints. Choose the events it receives, or "*" for all of them, including event types added later. Store the signing secret: it's shown once.

  • The URL must be https, at most 500 characters, and publicly reachable. Flux won't send to loopback, private, link-local or carrier-grade NAT addresses, and checks again on every delivery.
  • Flux doesn't follow redirects. A 3xx counts as a failure.
  • Webhooks are sent only to organizations with at least one active endpoint. Events from before an endpoint existed aren't replayed to it.

The request#

Each delivery is a POST with a JSON body.

HeaderValue
Content-Typeapplication/json
User-AgentFluxERP-Webhooks/1
Flux-Event-IdThe event id, evt_…. Also in the body as id.
Flux-Event-TypeThe event type, such as invoice.paid. Also in the body as type.
Flux-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256>. See Verify signatures.
Body (formatted here; Flux sends compact JSON)
{
  "id": "evt_01JC8Z5T6Q1W3M4N5P6R7S8T9V",
  "type": "invoice.paid",
  "created": "2026-11-01T10:20:00.4121933Z",
  "data": {
    "object": {
      "…": "the invoice object"
    }
  }
}
FieldDescription
idstringevt_ followed by a ULID: unique and sortable by time. Deduplicate on it.
typestringThe event type, such as invoice.paid.
createdtimestamp (UTC)When the event happened. UTC, with Z.
dataobject{ "object": … }: the object for this event type.

The object in data.object depends on the event type. See the Event catalogue.

Responding, retries and ordering#

Respond within 10 seconds#

Return any 2xx status within 10 seconds. Anything else, a redirect, or a timeout is a failure. Do slow work after you respond: put the event on a queue and return 200.

Retries#

After a failure, Flux retries on this schedule. After the 10th failed attempt, the delivery is marked dead. You can send it again from the delivery log, in the app or with POST /webhook-deliveries/{id}/redeliver.

AttemptWhenTime since the event
1At once0
21 minute later1 min
35 minutes later6 min
430 minutes later36 min
52 hours later2 h 36 min
66 hours later8 h 36 min
712 hours later20 h 36 min
81 day later1 d 20 h 36 min
91 day later2 d 20 h 36 min
101 day later3 d 20 h 36 min

Auto-disable#

After 50 failed attempts in a row, across all its deliveries, Flux switches the endpoint off (active: false, with a disabledReason). It emails the person who registered the endpoint (for an endpoint registered through the API, the admin who created the key) and the organization's support email. Once you've fixed it, re-enable it with PATCH {"active": true} or in the app; that also resets the failure count. Then redeliver what you missed.

At least once, in any order#

  • Duplicates happen. A delivery can arrive more than once, and Flux re-sends invoice events whose sending was interrupted (it looks back 48 hours). Deduplicate on Flux-Event-Id.
  • Order isn't guaranteed. invoice.paid can arrive before invoice.approved. When the current state matters, re-read the object from the API.
  • Ignore event types you don't handle, and fields you don't know.

Testing#

  • Send a webhook.test event with POST /webhook-endpoints/{id}/test, or the Test button in the app. It's signed and retried like any other event.
  • Check each attempt's status code and error in the delivery log.
  • To receive webhooks on your laptop, expose your local server through an HTTPS tunnel and register the tunnel's URL on a staging organization.
  • Use the signature checker to sign a sample body and send it to your endpoint yourself.