curl --request PATCH \
--url https://api.blujay.io/v1/webhook-subscriptions/{subscription_id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"event_types": [
"message.accepted",
"message.delivered",
"message.read"
]
}
'const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({event_types: ['message.accepted', 'message.delivered', 'message.read']})
};
fetch('https://api.blujay.io/v1/webhook-subscriptions/{subscription_id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.blujay.io/v1/webhook-subscriptions/{subscription_id}"
payload = { "event_types": ["message.accepted", "message.delivered", "message.read"] }
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.blujay.io/v1/webhook-subscriptions/{subscription_id}"
payload := strings.NewReader("{\n \"event_types\": [\n \"message.accepted\",\n \"message.delivered\",\n \"message.read\"\n ]\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))
}require 'uri'
require 'net/http'
url = URI("https://api.blujay.io/v1/webhook-subscriptions/{subscription_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"event_types\": [\n \"message.accepted\",\n \"message.delivered\",\n \"message.read\"\n ]\n}"
response = http.request(request)
puts response.read_body<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.blujay.io/v1/webhook-subscriptions/{subscription_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'event_types' => [
'message.accepted',
'message.delivered',
'message.read'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}{
"status": "success",
"trace_id": "trc_8f2a1c0e4b7d",
"timestamp": "2026-10-14T17:01:00.000Z",
"data": {
"subscription_id": "whsub_abc123",
"url": "https://example.com/webhooks/blujay",
"event_types": [
"message.accepted"
],
"phone_number_filter": {
"mode": "all"
},
"webhook_version": "2026-06-14",
"status": "active",
"signing_secret_hint": "Y3OA==",
"created_at": "2026-10-14T17:01:00.000Z",
"updated_at": "2026-10-14T17:01:00.000Z"
},
"client_request_id": "<string>"
}Update a webhook subscription
Changes only the fields you send. Set status: disabled to pause a subscription; disabled subscriptions don’t receive new events. Changing webhook_version affects only events created after the change.
Errors match create: UNSUPPORTED_WEBHOOK_EVENT, FEATURE_NOT_ENABLED for event types you can’t add, and the WEBHOOK_URL_* codes for a bad URL.
curl --request PATCH \
--url https://api.blujay.io/v1/webhook-subscriptions/{subscription_id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"event_types": [
"message.accepted",
"message.delivered",
"message.read"
]
}
'const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({event_types: ['message.accepted', 'message.delivered', 'message.read']})
};
fetch('https://api.blujay.io/v1/webhook-subscriptions/{subscription_id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.blujay.io/v1/webhook-subscriptions/{subscription_id}"
payload = { "event_types": ["message.accepted", "message.delivered", "message.read"] }
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.blujay.io/v1/webhook-subscriptions/{subscription_id}"
payload := strings.NewReader("{\n \"event_types\": [\n \"message.accepted\",\n \"message.delivered\",\n \"message.read\"\n ]\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))
}require 'uri'
require 'net/http'
url = URI("https://api.blujay.io/v1/webhook-subscriptions/{subscription_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"event_types\": [\n \"message.accepted\",\n \"message.delivered\",\n \"message.read\"\n ]\n}"
response = http.request(request)
puts response.read_body<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.blujay.io/v1/webhook-subscriptions/{subscription_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'event_types' => [
'message.accepted',
'message.delivered',
'message.read'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}{
"status": "success",
"trace_id": "trc_8f2a1c0e4b7d",
"timestamp": "2026-10-14T17:01:00.000Z",
"data": {
"subscription_id": "whsub_abc123",
"url": "https://example.com/webhooks/blujay",
"event_types": [
"message.accepted"
],
"phone_number_filter": {
"mode": "all"
},
"webhook_version": "2026-06-14",
"status": "active",
"signing_secret_hint": "Y3OA==",
"created_at": "2026-10-14T17:01:00.000Z",
"updated_at": "2026-10-14T17:01:00.000Z"
},
"client_request_id": "<string>"
}Authorizations
Your workspace API key, sent as Authorization: Bearer blu_.... Workspace admins can find it in the Blujay app under API.
An API key has full access to your workspace and works on every endpoint. There is no test mode: every key sends real messages and changes real data, so keep it secret and use it only from your server.
Each API key can make 600 read requests and 120 write requests per minute, plus 60 attachment requests per minute. Over a limit you get 429 RATE_LIMIT_EXCEEDED with Retry-After.
Path Parameters
^whsub_[A-Za-z0-9_-]+$"whsub_abc123"
Body
Your HTTPS endpoint, up to 2,048 characters. It must resolve to a public address; localhost and private, link-local and cloud metadata addresses are rejected.
2048"https://example.com/webhooks/blujay"
Replaces the subscription's event list. Omit it to keep the current list. You can retain or remove event types already on the subscription, but any type you add must be one you can subscribe to (see WebhookSubscribableEventType); otherwise you get FEATURE_NOT_ENABLED.
1 - 100 elementsEvery event type that can appear on a subscription or in a webhook. Some types are recognized only so older subscriptions keep working and may never be sent. You can add only the types in WebhookSubscribableEventType.
message.accepted, message.sent, message.delivered, message.read, message.failed, message.received, reaction.added, reaction.removed, participant.added, participant.removed, chat.group_name_updated, phone_number.status_updated, contact.tag_added, contact.assigned, chat.typing_indicator.started, chat.typing_indicator.stopped, message.edited, message.unsent Which of your numbers this subscription gets events for. all covers every number, including numbers you add later; selected covers only the phone_number_ids you list. Contact events are always sent for the whole workspace. Defaults to all on create; left unchanged on update if you omit it.
- Option 1
- Option 2
Show child attributes
Show child attributes
The payload version for a subscription's events.
2026-08-20reports the service you asked for asrequested_serviceand the service actually used asservice.2026-09-02adds messageparts.2026-10-03adds iMessage group typing with aparticipantssnapshot, andreply_toon message events.
Earlier versions keep their original payload shapes and never receive group typing. Each event uses the subscription's version at the time the event was created, so changing the version affects only later events.
2026-06-14, 2026-08-01, 2026-08-20, 2026-09-02, 2026-10-03 active, disabled Response
The updated subscription.
Wraps every successful response. The result is in data.
success ^trc_[A-Za-z0-9_-]+$"trc_8f2a1c0e4b7d"
"2026-10-14T17:01:00.000Z"
Show child attributes
Show child attributes
Your X-Request-ID header value, echoed back when you send one. Not included on the send responses (POST /v1/chats, POST /v1/chats/{chat_id}/messages, POST /v1/messages/{message_id}/retry); keep your own mapping from your request ID to the returned message ID there.