Skip to main content
Outbound webhooks let your systems react to events in Flowella without polling the API. Each webhook has a URL, a signing secret and a list of event types. When one of those events happens, Flowella sends a signed JSON POST to the URL. You can manage webhooks in the app under Settings → Webhooks (see Add and manage outbound webhooks) or with the webhooks API. This page is the reference for what Flowella sends.

Event types

Tick these events when you add a webhook, or pass them in events when you create one with the API. Contact events fire only for these single-contact API calls. They do not fire for POST /v1/contacts/bulk-upsert, for contacts created by inbound messages, or for changes made in the app or synced from HubSpot.
Known issue: optout.created doesn’t fire for a contact’s first opt-out on a WhatsApp number, whether the contact sends a keyword or you call POST /v1/opt-outs. At the moment it fires only when a contact who opted back in opts out again. Don’t rely on it to catch every opt-out: list opt-outs with GET /v1/opt-outs (scope contacts:read).
Flowella also sends webhook.test when you select Send test event in the app or call POST /v1/webhooks/{id}/test. You cannot subscribe to it. Its data is { "test": true, "sentAt": "<ISO 8601 time>" }.
One outbound message can produce two message.sent events: one when Flowella hands it to Meta, and one when Meta reports it as sent. The two have different top-level id values. Use data.messageId to link them to the same message.

Request format

Flowella sends each event as an HTTP POST with these headers: Flowella sends no other custom headers. The event type and ID are in the body.

Body

Every webhook has a payload version, returned as payloadVersion by the webhooks API. Version 1 is the default for every webhook, and there is no setting in the app or the API to change it. The two versions use these envelopes:
Flowella does not send an event whose data is larger than 256 KB.

Verify signatures

Check the signature on every request before you trust it. Flowella computes X-Flowella-Signature like this:
  • Algorithm: HMAC with SHA-256.
  • Key: the signing secret as UTF-8 text, exactly as saved. A secret generated by the API includes its whsec_ prefix.
  • Message: the raw request body, byte for byte, as Flowella sent it. Nothing else is signed: there is no timestamp or URL in the signature.
  • Output: lowercase hexadecimal, 64 characters, with no sha256= prefix.
To verify, compute the same HMAC over the raw body and compare it with the header in constant time. During the 24 hours after an API rotation, the request carries both headers: accept it if either header matches a secret you hold.
Compute the signature over the raw body before any middleware parses or reformats it. If your framework parses the JSON and you serialise it again, the bytes change and the signature does not match.

Responses, retries and timeouts

Flowella delivers several events at once, so they can arrive out of order. Use created_at or occurred_at if order matters. Reply with a 2xx status as soon as the signature checks out, then process the event in the background. For how retries look in the app, see Retries and back-off.

Deduplicate events

Your endpoint can receive the same event more than once: a retry after your endpoint timed out but still processed the request, or a replay through the API. Deduplicate on the top-level id:
  1. Store the id of every event you process.
  2. If an incoming id is already stored, reply 200 and do nothing else.
  3. Keep stored IDs at least as long as you might replay deliveries.
Do not deduplicate on data.messageId. Different events about the same message, such as message.sent and message.delivered, share it.

Event feed

The event feed is a pull alternative to webhooks. GET /v1/events returns events from the last 7 days, including events that no webhook subscribes to. It needs the events:read scope. Filter with type (one type or a comma-separated list), occurredAfter and occurredBefore, and page with limit and cursor. Each item has id, type, occurredAt, version, correlationId and data. The id matches the id in the webhook body, so you can use the feed to fill gaps after your endpoint was down.

Manage webhooks with the API

All webhook endpoints need an API key or OAuth access token with the webhooks:manage scope. Keys created in the app have it. See the API introduction for authentication. Webhook objects contain id, name, url, events, isActive, consecutiveFailures, signingSecretPrefix (the first 8 characters of the current secret), payloadVersion, lastTriggeredAt, disabledAt, createdAt and updatedAt. A few rules apply to webhooks managed through the API:
  • You cannot choose the signing secret through the API. POST /v1/webhooks generates one that starts with whsec_ and returns it once, as signingSecret.
  • Replays do not count towards or reset the failure count. Flowella delivers a replay only while the webhook is active and still subscribed to that event type.
  • The URL must follow the same URL rules as in the app.

Rotate a signing secret

Rotate a secret when you think it has leaked, or on a regular schedule. Rotation works for any webhook that has a secret, including webhooks whose secret you typed in the app.
1

Request a new secret

Call the rotate endpoint and store the signingSecret from the response. Flowella returns it only once.
2

Accept both secrets for 24 hours

New deliveries are signed with the new secret in X-Flowella-Signature, and with the old secret in X-Flowella-Signature-Previous. A handler that still uses the old secret keeps working if it checks both headers, as in the example in Verify signatures.
3

Switch to the new secret

Update your handler to the new secret within 24 hours. After that, Flowella stops sending X-Flowella-Signature-Previous.
Changing the secret in the app with New signing secret has no overlap: the old secret stops working at once.

Test locally

To receive webhooks on your own computer, expose your local server through a public tunnel, such as ngrok or Cloudflare Tunnel. Use the tunnel’s public URL as the endpoint. Then use Send test event to send requests whenever you need them. Flowella refuses local and private addresses, so a localhost URL does not work.

Manage webhooks in the app

Add endpoints, send test events and read the delivery log.

API introduction

Authentication, scopes, rate limits and errors.

API keys

Create the key you need for the webhooks API.

Developer hub

Recent API requests, quickstarts and the API sandbox.
Last modified on October 30, 2026