Prime Bot
Home Docs Webhooks

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

EventFires when
pingYou register or manually re-test the endpoint.
message.receivedAn inbound DM arrives on a connected Telegram account.
message.sentA campaign successfully sends an outbound message.
message.failedA send attempt fails terminally.
campaign.startedA campaign transitions to running.
campaign.pausedA campaign transitions to paused.
campaign.completedA broadcast finishes draining its contact list.
sender.repliedA 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

HeaderMeaning
X-PrimeBot-EventEvent name, e.g. message.received.
X-PrimeBot-DeliveryUnique ID for this delivery attempt. Use for idempotency.
X-PrimeBot-Signaturesha256=<hex> — HMAC-SHA256 of the raw request body.
X-PrimeBot-TimestampUnix 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.

Edit this page on GitHub