Receiving webhooks
Receive signed HTTPS POSTs on message and session events. Register URLs, verify signatures, act on events.
Webhooks push Blueticks events to your server in near real time. Each POST is HMAC-SHA256 signed with a secret you see once at creation.
This guide assumes you have an API key (Authentication) and an SDK client set up (Quickstart).
Event types
message.queued,message.sending,message.delivered,message.failed,message.readsession.connected,session.disconnectedcampaign.started,campaign.paused,campaign.resumed,campaign.completed,campaign.aborted
The enum also accepts legacy *_webhook event names for inbound and group
activity — e.g. new_message_received_webhook, message_reaction_webhook,
ack_changed_webhook, poll_vote_webhook, reply_to_my_message_webhook,
and the participant_* / group_* membership events. See the
Create webhook reference for the full
enum.
Register a webhook
created = bt.webhooks.create(
url="https://api.your-server.com/bt-webhooks",
events=["message.delivered", "message.failed"],
description="prod",
)
print(created.secret) # shown exactly once — store itconst { secret } = await bt.webhooks.create({
url: 'https://api.your-server.com/bt-webhooks',
events: ['message.delivered', 'message.failed'],
});$created = $bt->webhooks->create(
url: 'https://api.your-server.com/bt-webhooks',
events: ['message.delivered', 'message.failed'],
);
echo $created->secret; // shown oncecreated = client.webhooks.create(
url: "https://api.your-server.com/bt-webhooks",
events: ["message.delivered", "message.failed"],
description: "prod",
)
puts created.secret # shown exactly once — store itcreated, _ := c.Webhooks.Create(context.Background(), blueticks.CreateWebhookParams{
URL: "https://api.your-server.com/bt-webhooks",
Events: []string{"message.delivered", "message.failed"},
Description: "prod",
})
fmt.Println(created.Secret) // shown exactly once — store itcurl -X POST https://api.blueticks.co/v1/webhooks \
-H "Authorization: Bearer BLUETICKS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url":"https://api.your-server.com/bt-webhooks",
"events":["message.delivered","message.failed"]
}'The secret field returns only on create (and on rotate-secret).
After you dismiss the response, you can rotate via
POST /v1/webhooks/{id}/rotate-secret but you cannot retrieve the
previous secret again.
Payload shape
Every delivery POSTs:
{
"id": "evt_01h7...",
"type": "message.delivered",
"created_at": "2026-04-23T16:42:00.000Z",
"data": { /* same shape as the resource being reported */ }
}Headers:
Blueticks-Webhook-Id: wh_...
Blueticks-Webhook-Timestamp: 1714234567
Blueticks-Webhook-Signature: v1=<64-hex-sha256>Verify signatures
Three checks, all done by the SDK helper: reject if the timestamp is more than 5 minutes old, recompute HMAC, constant-time compare.
from blueticks.webhooks import verify, WebhookVerificationError
@app.post("/bt-webhooks")
def handle_webhook(request):
try:
event = verify(
payload=request.body, # raw bytes
headers=request.headers,
secret=os.environ["BT_WEBHOOK_SECRET"],
)
except WebhookVerificationError:
return Response(status=400)
if event.type == "message.delivered":
...
return Response(status=200)import { verifyWebhook, WebhookVerificationError } from 'blueticks/webhooks';
app.post('/bt-webhooks', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = verifyWebhook({
payload: req.body,
headers: req.headers,
secret: process.env.BT_WEBHOOK_SECRET!,
});
// handle event.type
res.sendStatus(200);
} catch (err) {
if (err instanceof WebhookVerificationError) return res.sendStatus(400);
throw err;
}
});use Blueticks\Webhooks;
use Blueticks\Errors\WebhookVerificationError;
try {
$event = Webhooks\verify(
file_get_contents('php://input'),
getallheaders(),
$_ENV['BT_WEBHOOK_SECRET'],
);
} catch (WebhookVerificationError $e) {
http_response_code(400); exit;
}require "blueticks"
begin
event = Blueticks::Webhooks.verify(
payload: request.raw_post, # raw bytes
headers: request.headers,
secret: ENV.fetch("BT_WEBHOOK_SECRET"),
)
rescue Blueticks::Errors::WebhookVerificationError
return [400, {}, []]
end
# handle event.typeimport (
"errors"
"net/http"
blueticks "github.com/serenix-com/blueticks-go"
)
event, err := blueticks.VerifyWebhook(rawBody, r.Header, secret)
if errors.Is(err, blueticks.ErrWebhookVerification) {
http.Error(w, "bad signature", http.StatusBadRequest)
return
}
// handle event.TypeRetries
Non-2xx responses or timeouts trigger up to 8 retries over ~24 h with
exponential backoff: 1m → 5m → 15m → 1h → 2h → 6h → 12h → 24h.
After 8 consecutive failures the webhook auto-disables — you'll need
to PATCH status: "enabled" to re-enable after fixing your endpoint.
Manage your webhooks
List every registered endpoint, fetch one by id, change its URL/events/status, or delete it:
curl -X GET 'https://api.blueticks.co/v1/webhooks' \ -H 'Authorization: Bearer BLUETICKS_API_KEY'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.webhooks.list()import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.webhooks.list();use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->webhooks->list();require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.webhooks.list()import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Webhooks.List(context.Background())curl -X GET 'https://api.blueticks.co/v1/webhooks/string' \ -H 'Authorization: Bearer BLUETICKS_API_KEY'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.webhooks.get( "id_01H7...",)import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.webhooks.get('id_01H7...');use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->webhooks->retrieve('id_01H7...');require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.webhooks.get( "id_01H7...",)import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Webhooks.Get(context.Background(), "id_01H7...")curl -X PATCH 'https://api.blueticks.co/v1/webhooks/string' \ -H 'Authorization: Bearer BLUETICKS_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "url": "https://example.com", "events": [ "message.queued" ], "description": "string", "status": "enabled"}'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.webhooks.update( "id_01H7...", # request body fields…,)import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.webhooks.update('id_01H7...', { /* … */ });use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->webhooks->update('id_01H7...', /* opts */);require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.webhooks.update( "id_01H7...", # request body fields…,)import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Webhooks.Update(context.Background(), "id_01H7...", params){ "url": "https://example.com", "events": [ "message.queued" ], "description": "string", "status": "enabled"}curl -X DELETE 'https://api.blueticks.co/v1/webhooks/string' \ -H 'Authorization: Bearer BLUETICKS_API_KEY'import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.webhooks.delete( "id_01H7...",)import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.webhooks.delete('id_01H7...');use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->webhooks->delete('id_01H7...');require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.webhooks.delete( "id_01H7...",)import ( "context" blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Webhooks.Delete(context.Background(), "id_01H7...")To re-enable an auto-disabled endpoint, PATCH it with status: "enabled"
once your server is healthy again.
Rotate the secret
If your secret leaks, rotate it. The old secret is invalidated immediately; all pending retries sign with the new one.
result = bt.webhooks.rotate_secret("wh_123")
print(result.secret) # store immediately — shown only onceconst result = await bt.webhooks.rotateSecret('wh_123');
console.log(result.secret); // store immediately — shown only once$result = $bt->webhooks->rotateSecret('wh_123');
echo $result->secret; // store immediately — shown only onceresult = client.webhooks.rotate_secret("wh_123")
puts result.secret # store immediately — shown only onceresult, _ := c.Webhooks.RotateSecret(context.Background(), "wh_123")
fmt.Println(result.Secret) // store immediately — shown only oncecurl -X POST https://api.blueticks.co/v1/webhooks/wh_123/rotate-secret \
-H "Authorization: Bearer BLUETICKS_API_KEY"