SMMFor

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

HeaderValue
Content-Typeapplication/json
User-AgentSMMPanel-Webhook/1
X-TB-Eventorder.status
X-TB-TimestampUnix timestamp (seconds)
X-TB-Signaturesha256=<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 2xx response 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.