curl --request POST \
--url https://api.flow.engineer/v0/webhook_endpoints/{webhook_endpoint_id}/rotate_secret \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{}'{
"id": "we_01JB8ZF6P8R0T2W4Y6A8C0E2F4",
"url": "<string>",
"events": [
"message.received"
],
"enabled": true,
"livemode": true,
"created_at": "2023-11-07T05:31:56Z",
"description": "<string>",
"secret": "<string>",
"previous_secret_expires_at": "2023-11-07T05:31:56Z"
}{
"error": {
"type": "invalid_request",
"message": "limit must be between 1 and 100.",
"hint": "Pass limit between 1 and 100 (default 20), and page with after or before.",
"doc_url": "https://api.flow.engineer/docs/errors/invalid_request",
"param": "limit"
}
}{
"error": {
"type": "authentication",
"message": "No valid API key was given.",
"hint": "Send the header Authorization: Bearer fk_test_... (or fk_live_...); no key yet? Get a test key with curl -X POST https://api.flow.engineer/v0/sandbox/keys",
"doc_url": "https://api.flow.engineer/docs/errors/authentication"
}
}{
"error": {
"type": "invalid_request",
"message": "<string>",
"hint": "Send a template instead: POST /v0/conversations/{conversation_id}/template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"channel": "telegram",
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}{
"error": {
"type": "idempotency_conflict",
"message": "This Idempotency-Key was already used for a different request (another method, path or body).",
"hint": "Use a new Idempotency-Key for this request; reuse a key only to repeat the identical request.",
"doc_url": "https://api.flow.engineer/docs/errors/idempotency_conflict",
"param": "Idempotency-Key",
"channel_code": "body_mismatch"
}
}{
"error": {
"type": "invalid_request",
"message": "The request body is larger than 33554432 bytes.",
"hint": "Send a smaller body; upload media with POST /v0/files and send its file_id instead of inlining it.",
"doc_url": "https://api.flow.engineer/docs/errors/invalid_request"
}
}{
"error": {
"type": "new_contact_limit",
"message": "This sender has started its 15 new conversations for today.",
"hint": "Retry after 3600 seconds; replies into existing conversations still go.",
"doc_url": "https://api.flow.engineer/docs/errors/new_contact_limit",
"retry_after": 3600,
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T"
}
}{
"error": {
"type": "invalid_request",
"message": "<string>",
"hint": "Send a template instead: POST /v0/conversations/{conversation_id}/template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"channel": "telegram",
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}Rotate a webhook endpoint's signing secret
Makes a new signing secret for the endpoint and returns it in secret, shown
only this once. The previous secret keeps signing for overlap_seconds
(default 86400, one day; 0 retires it at once): during the overlap every
delivery’s Flow-Signature carries two v1 values, one per secret, so you
can deploy the new secret while the old one still verifies. When the overlap
ends only the new secret signs. Rotating again during an overlap retires the
older previous secret at once: at most two secrets are ever active. So send an
Idempotency-Key and retry with the same key: a retry without one is a second
rotation, which retires the secret you still have deployed. The new secret is
never stored with the key, so a repeat of a rotation that went through answers
409 idempotency_conflict instead of showing the secret again.
curl --request POST \
--url https://api.flow.engineer/v0/webhook_endpoints/{webhook_endpoint_id}/rotate_secret \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{}'{
"id": "we_01JB8ZF6P8R0T2W4Y6A8C0E2F4",
"url": "<string>",
"events": [
"message.received"
],
"enabled": true,
"livemode": true,
"created_at": "2023-11-07T05:31:56Z",
"description": "<string>",
"secret": "<string>",
"previous_secret_expires_at": "2023-11-07T05:31:56Z"
}{
"error": {
"type": "invalid_request",
"message": "limit must be between 1 and 100.",
"hint": "Pass limit between 1 and 100 (default 20), and page with after or before.",
"doc_url": "https://api.flow.engineer/docs/errors/invalid_request",
"param": "limit"
}
}{
"error": {
"type": "authentication",
"message": "No valid API key was given.",
"hint": "Send the header Authorization: Bearer fk_test_... (or fk_live_...); no key yet? Get a test key with curl -X POST https://api.flow.engineer/v0/sandbox/keys",
"doc_url": "https://api.flow.engineer/docs/errors/authentication"
}
}{
"error": {
"type": "invalid_request",
"message": "<string>",
"hint": "Send a template instead: POST /v0/conversations/{conversation_id}/template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"channel": "telegram",
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}{
"error": {
"type": "idempotency_conflict",
"message": "This Idempotency-Key was already used for a different request (another method, path or body).",
"hint": "Use a new Idempotency-Key for this request; reuse a key only to repeat the identical request.",
"doc_url": "https://api.flow.engineer/docs/errors/idempotency_conflict",
"param": "Idempotency-Key",
"channel_code": "body_mismatch"
}
}{
"error": {
"type": "invalid_request",
"message": "The request body is larger than 33554432 bytes.",
"hint": "Send a smaller body; upload media with POST /v0/files and send its file_id instead of inlining it.",
"doc_url": "https://api.flow.engineer/docs/errors/invalid_request"
}
}{
"error": {
"type": "new_contact_limit",
"message": "This sender has started its 15 new conversations for today.",
"hint": "Retry after 3600 seconds; replies into existing conversations still go.",
"doc_url": "https://api.flow.engineer/docs/errors/new_contact_limit",
"retry_after": 3600,
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T"
}
}{
"error": {
"type": "invalid_request",
"message": "<string>",
"hint": "Send a template instead: POST /v0/conversations/{conversation_id}/template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"channel": "telegram",
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}Authorizations
An API key of one app, sent as Authorization: Bearer <key>. Keys start with
fk_test_ (test mode: sandbox senders and test data only) or fk_live_
(live mode). Keep live keys on your server; never ship them in an app or page.
Headers
A unique string (up to 255 characters) that makes this request safe to retry. A repeat with the same key within 24 hours returns the first answer instead of acting again. See "Idempotency" in the introduction.
1 - 255The API version to use, as a date. Without it, the version pinned to your app when it was created is used.
"2026-11-01"
Path Parameters
The webhook endpoint's ID.
A webhook endpoint ID, we_ and a ULID.
^we_[0-9A-HJKMNP-TV-Z]{26}$"we_01JB8ZF6P8R0T2W4Y6A8C0E2F4"
Body
Options for rotating a webhook endpoint's signing secret.
How long the previous secret keeps signing alongside the new one, in seconds (at most 7 days). 0 retires it at once.
0 <= x <= 604800Response
The secret was rotated. secret is the new secret, included this once; previous_secret_expires_at says when the old one stops signing.
A URL that receives your app's events of the types it subscribes to.
A webhook endpoint ID, we_ and a ULID.
^we_[0-9A-HJKMNP-TV-Z]{26}$"we_01JB8ZF6P8R0T2W4Y6A8C0E2F4"
The HTTPS URL events are posted to.
The event types delivered to this endpoint.
message.received: the contact sent a message (data.message.content, withkind:basic,voice,button_replyfor a tap on a button or list row,reaction,location,contact_cardorfile_blocked).message.sent,message.delivered,message.read,message.failed: the status of your outbound messages.message.failedcarries the error indata.message.error.reaction.added,reaction.removed: the contact reacted to a message.typing.started,typing.stopped: the contact is typing, where the channel reports it.conversation.started: the first inbound message from a new contact, a sandbox join (viasandbox_join: Flow itself confirms the join, then the join message reaches you as the contact's firstmessage.received), or your first send to a new contact (viaoutbound).conversation.window_closing: WhatsApp only, opt-in. The 24-hour window closes in 1 hour.sender.status_changed: a sender was throttled, flagged, banned or restored, or its WhatsApp quality rating changed.template.status_changed: Meta approved, rejected or paused a template.contact.deleted: a contact was deleted withDELETE /v0/contacts/{contact_id}. Carries only the contact's ID; delete what you keep about them too.contact.left: a person on a shared sender (the sandbox) joined another app, or the other mode of your app, by sending its join message. Nothing more reaches you from them, and sends to them fail withsandbox_contact_left, until they send your join message again.call.received: iMessage only. A contact tried to FaceTime the line. The line cannot answer it, and the call does not count as the contact writing: a reply-only line can message them only after they write.
message.received, message.sent, message.delivered, message.read, message.failed, reaction.added, reaction.removed, typing.started, typing.stopped, conversation.started, conversation.window_closing, sender.status_changed, template.status_changed, contact.deleted, contact.left, call.received Whether events are delivered. Disabled endpoints keep their place; nothing is lost from the log.
Whether the endpoint receives live-mode or test-mode events.
When the endpoint was created.
Your note about the endpoint.
The signing secret (whsec_...). Only in the answers to create and to rotate the secret.
While a secret rotation overlaps, when the previous secret stops signing. Absent when only one secret is active.