curl --request PATCH \
--url https://api.flow.engineer/v1/webhook_endpoints/{webhook_endpoint_id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"url": "<string>",
"events": [],
"description": "<string>",
"enabled": true
}
'const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({url: '<string>', events: [], description: '<string>', enabled: true})
};
fetch('https://api.flow.engineer/v1/webhook_endpoints/{webhook_endpoint_id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.flow.engineer/v1/webhook_endpoints/{webhook_endpoint_id}"
payload = {
"url": "<string>",
"events": [],
"description": "<string>",
"enabled": True
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.flow.engineer/v1/webhook_endpoints/{webhook_endpoint_id}"
payload := strings.NewReader("{\n \"url\": \"<string>\",\n \"events\": [],\n \"description\": \"<string>\",\n \"enabled\": true\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"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/v1/sandbox/keys",
"doc_url": "https://api.flow.engineer/docs/errors/authentication"
}
}{
"error": {
"type": "invalid_request",
"message": "<string>",
"hint": "Send a template instead: POST /v1/messages with content.type=template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}{
"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 /v1/messages with content.type=template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}Update a webhook endpoint
Changes an endpoint’s URL, event types, description, or whether it is enabled. Fields you leave out are kept. A url another endpoint of the app already has in this mode answers 400 invalid_request with param url.
curl --request PATCH \
--url https://api.flow.engineer/v1/webhook_endpoints/{webhook_endpoint_id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"url": "<string>",
"events": [],
"description": "<string>",
"enabled": true
}
'const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({url: '<string>', events: [], description: '<string>', enabled: true})
};
fetch('https://api.flow.engineer/v1/webhook_endpoints/{webhook_endpoint_id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.flow.engineer/v1/webhook_endpoints/{webhook_endpoint_id}"
payload = {
"url": "<string>",
"events": [],
"description": "<string>",
"enabled": True
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.flow.engineer/v1/webhook_endpoints/{webhook_endpoint_id}"
payload := strings.NewReader("{\n \"url\": \"<string>\",\n \"events\": [],\n \"description\": \"<string>\",\n \"enabled\": true\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"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/v1/sandbox/keys",
"doc_url": "https://api.flow.engineer/docs/errors/authentication"
}
}{
"error": {
"type": "invalid_request",
"message": "<string>",
"hint": "Send a template instead: POST /v1/messages with content.type=template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}{
"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 /v1/messages with content.type=template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"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
The 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
Changes to a webhook endpoint. Fields left out are kept.
An HTTPS URL on a public address.
2048The event types to deliver.
1message.received: the contact sent something with content (a button tap arrives asbutton_replycontent).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, or a sandbox join (Flow itself answers the join; the join message is not amessage.received).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.
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 Your note about the endpoint.
500Whether to deliver events.
Response
The updated webhook endpoint.
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 something with content (a button tap arrives asbutton_replycontent).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, or a sandbox join (Flow itself answers the join; the join message is not amessage.received).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.
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 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.