Webhooks
Instant notifications to a reseller's server when an order changes status; payload, signature verification and retry rules.
What they do
Customers using the API (resellers) can register a webhook URL instead of polling status. Whenever an order's status changes, the panel sends a signed POST to that URL.
Webhooks are managed on Customer panel → API & Reseller:
- Up to 3 URLs per customer.
- The URL must be
https://and point to a public server (local/private addresses are refused; DNS is re-checked at delivery time). - The secret (
whsec_+ 48 characters) is shown once on creation and can be rotated later. - The last 10 deliveries are shown per URL.
Event and payload
There is one event: order.status.
{
"event": "order.status",
"order": 12345,
"service": 101,
"link": "https://instagram.com/account",
"quantity": 1000,
"charge": "0.5",
"status": "Completed",
"start_count": "0",
"remains": "0",
"currency": "USD",
"created_at": "2026-10-05T10:00:00.000Z",
"sent_at": "2026-10-05T10:05:12.000Z"
}
status uses the API v2 values: Pending, In progress, Completed, Partial, Canceled. Cost, profit and the provider's order number are never sent.
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | SMMPanel-Webhook/1 |
X-TB-Event | order.status |
X-TB-Timestamp | Unix timestamp (seconds) |
X-TB-Signature | sha256=<hex> |
Verifying the signature
The signature is the HMAC-SHA256 of timestamp + "." + raw_body, keyed with your secret. Verify against the raw body, before parsing and re-serialising JSON.
<?php
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_TB_TIMESTAMP'];
$sig = $_SERVER['HTTP_X_TB_SIGNATURE'];
$calc = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $secret);
if (!hash_equals($calc, $sig)) { http_response_code(401); exit; }
// Optional: reject old requests
if (abs(time() - (int)$ts) > 300) { http_response_code(401); exit; }
$event = json_decode($body, true);
http_response_code(200);
import crypto from "node:crypto";
import express from "express";
const app = express();
app.post("/panel-webhook", express.raw({ type: "application/json" }), (req, res) => {
const ts = req.header("X-TB-Timestamp");
const sig = req.header("X-TB-Signature") || "";
const calc = "sha256=" + crypto.createHmac("sha256", process.env.WHSEC).update(`${ts}.${req.body}`).digest("hex");
const ok = sig.length === calc.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(calc));
if (!ok) return res.sendStatus(401);
const event = JSON.parse(req.body.toString());
// event.order, event.status …
res.sendStatus(200);
});
Delivery and retries
- Each request times out after 10 seconds; redirects are not followed.
- Any
2xxresponse counts as success. - Failed deliveries are retried up to 8 times with exponential backoff (1 min, 2 min, 4 min… up to 6 hours).
- After 20 consecutive failures the URL is disabled automatically; re-enabling it in the panel resets the counter.
- Delivery records are kept for 14 days.
The same event may arrive more than once (for example if your response times out). Make your handler idempotent on order + status.