Webhooks
Push-based event delivery with HMAC-signed payloads. Avoid polling entirely.
Webhooks let Prime Bot POST events to an HTTPS endpoint you control as they happen. Every payload is signed with an HMAC-SHA256 secret that you pick when you register the endpoint, so you can verify the call genuinely came from us.
Register an endpoint
In the dashboard, go to Settings → Webhooks → Add endpoint. You'll pick:
- The destination URL (must be HTTPS).
- A signing secret (32+ characters, auto-generated by default).
- The subset of events you want to receive.
Prime Bot will send a ping event immediately; your endpoint must respond with a 2xx within 5 seconds or the registration fails.
Event list
| Event | Fires when |
|---|---|
ping | You register or manually re-test the endpoint. |
message.received | An inbound DM arrives on a connected Telegram account. |
message.sent | A campaign successfully sends an outbound message. |
message.failed | A send attempt fails terminally. |
campaign.started | A campaign transitions to running. |
campaign.paused | A campaign transitions to paused. |
campaign.completed | A broadcast finishes draining its contact list. |
sender.replied | A contact responds while a sequence is active (sequences auto-pause). |
Payload shape
{
"id": "evt_01H8Q3...",
"type": "message.received",
"created_at": "2026-04-21T16:02:41Z",
"data": {
"campaign_id": 1,
"sender_id": 904,
"telegram_message_id": "1827361231",
"body": "what's the pricing like?"
}
}
Request headers
| Header | Meaning |
|---|---|
X-PrimeBot-Event | Event name, e.g. message.received. |
X-PrimeBot-Delivery | Unique ID for this delivery attempt. Use for idempotency. |
X-PrimeBot-Signature | sha256=<hex> — HMAC-SHA256 of the raw request body. |
X-PrimeBot-Timestamp | Unix seconds when we signed the payload. Reject anything older than ~5 minutes to prevent replay. |
Verifying the signature
Compute HMAC-SHA256 of the raw, unparsed request body using your signing secret, then compare against the hex value after sha256= in X-PrimeBot-Signature. Always use a constant-time comparison.
PHP
<?php
$raw = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PRIMEBOT_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $raw, getenv('PRIMEBOT_SECRET'));
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('invalid signature');
}
$event = json_decode($raw, true);
// ... handle $event ...
http_response_code(204);
Node.js (Express)
import express from 'express';
import crypto from 'node:crypto';
const app = express();
app.post(
'/webhooks/primebot',
express.raw({ type: 'application/json' }),
(req, res) => {
const received = req.header('X-PrimeBot-Signature') || '';
const expected =
'sha256=' +
crypto
.createHmac('sha256', process.env.PRIMEBOT_SECRET)
.update(req.body)
.digest('hex');
const ok =
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!ok) return res.status(401).send('invalid signature');
const event = JSON.parse(req.body.toString('utf8'));
// ... handle event ...
res.status(204).end();
}
);
Python (Flask)
import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["PRIMEBOT_SECRET"].encode()
@app.post("/webhooks/primebot")
def handle():
raw = request.get_data()
received = request.headers.get("X-PrimeBot-Signature", "")
expected = "sha256=" + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(received, expected):
abort(401)
event = request.get_json(force=True)
# ... handle event ...
return "", 204
Retries
A non-2xx response (or no response within 10 seconds) is retried with exponential backoff at 30s, 2m, 10m, 1h, 6h, 24h. After the final failure the delivery is dropped and the endpoint is marked degraded — you'll get an email and a banner in the dashboard.
Always return 2xx as fast as possible and process the event off a queue. Long-running handlers cause retries and duplicate delivery.