Blueticks

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.read
  • session.connected, session.disconnected
  • campaign.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 it
const { 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 once
created = 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 it
created, _ := 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 it
curl -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.type
import (
	"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.Type

Retries

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 once
const 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 once
result = client.webhooks.rotate_secret("wh_123")
puts result.secret # store immediately — shown only once
result, _ := c.Webhooks.RotateSecret(context.Background(), "wh_123")
fmt.Println(result.Secret) // store immediately — shown only once
curl -X POST https://api.blueticks.co/v1/webhooks/wh_123/rotate-secret \
  -H "Authorization: Bearer BLUETICKS_API_KEY"