notpanel
ServicesPricingFAQGiveaway
notpanel

The fastest and most affordable SMM panel. Trusted by 1M+ users worldwide.

Product

  • Services
  • Pricing
  • Why NotPanel
  • About
  • Developers
  • Blog
  • FAQ

Legal

  • Terms of Service
  • Privacy Policy
  • Refund Policy

Connect

  • Contact Us
  • support@notpanel.com

© © 2026 NotPanel. All rights reserved.

API documentation
+
API Documentation

Introduction

  • Overview
  • Getting started
  • Authentication
  • Rate limits
  • Errors

Catalog

  • List services

Orders

  • Place order
  • Order status
  • Refill
  • Cancel

Account

  • Balance

Webhooks

  • Manage webhooks

Reference

  • Changelog
  • SDKs & libraries

Need help?

support@notpanel.com →

Register webhook

Subscribes a public URL to one or more events. Each endpoint receives its own HMAC-SHA256 secret — used to sign every outbound delivery, with replay-window protection. The secret is shown once at creation; if you lose it, regenerate by removing and re-adding the endpoint.

POSThttps://notpanel.com/api/v2action=webhook.add
API key requiredRate limited (per-key + per-tier + per-IP)Body: application/x-www-form-urlencoded
Maximum 5 webhooks per API key. URLs must be publicly reachable, must be https (http accepted in dev only), and must not resolve to private/loopback IPs — we do DNS resolution at registration time and reject internal addresses to prevent SSRF.

Parameters

NameTypeDescription
keyRequiredstringYour API key.
actionRequiredstringMust be the literal string "webhook.add".
urlRequiredstring (URL)Public HTTPS endpoint that will receive POSTs. Must be reachable from the public internet.
eventsstring (CSV)Comma-separated list of events to subscribe to. Defaults to all events. See list below.

Example request

Replace YOUR_API_KEYwith the key generated under your dashboard’s API page.

curl -X POST https://notpanel.com/api/v2 \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "key=YOUR_API_KEY&action=webhook.add&url=https://your-server.example.com/notpanel-webhook&events=order.completed,order.failed,balance.low"
const res = await fetch("https://notpanel.com/api/v2", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    key: "YOUR_API_KEY",
    action: "webhook.add",
    url: "https://your-server.example.com/notpanel-webhook",
    events: "order.completed,order.failed,balance.low",
  }),
});

const data = await res.json();
console.log(data);
import requests

res = requests.post(
  "https://notpanel.com/api/v2",
  data={
    "key": "YOUR_API_KEY",
    "action": "webhook.add",
    "url": "https://your-server.example.com/notpanel-webhook",
    "events": "order.completed,order.failed,balance.low",
},
)
print(res.json())
<?php
$body = http_build_query([
    'key' => 'YOUR_API_KEY',
    'action' => 'webhook.add',
    'url' => 'https://your-server.example.com/notpanel-webhook',
    'events' => 'order.completed,order.failed,balance.low',
]);

$response = file_get_contents('https://notpanel.com/api/v2', false, stream_context_create([
    'http' => [
        'method'  => 'POST',
        'header'  => "Content-Type: application/x-www-form-urlencoded\r\n",
        'content' => $body,
        'ignore_errors' => true,
    ],
]));

print_r(json_decode($response, true));

Example response

{
  "webhook_id": "whk_01HG9X...",
  "url": "https://your-server.example.com/notpanel-webhook",
  "secret": "rotate-this-immediately-it-is-shown-once",
  "events": [
    "order.completed",
    "order.partial",
    "order.cancelled",
    "order.failed",
    "order.processing",
    "order.in_progress",
    "order.refunded",
    "order.refill_completed",
    "balance.low"
  ]
}

Common errors

StatusBodyCause
400{"error":"Maximum 5 webhook endpoints per API key"}Existing endpoints exhausted the cap. Remove an unused endpoint with action=webhook.remove first.
400{"error":"Invalid URL"}URL was not http/https, did not parse, or resolved to a private/loopback IP.
400{"error":"Invalid event: foo"}Event name is not on the allow-list. Re-check spelling — names use the form "order.<status>".

Available events

  • order.completed
  • order.partial
  • order.cancelled
  • order.failed
  • order.processing
  • order.in_progress
  • order.refunded
  • order.refill_completed
  • balance.low

List webhooks

action=webhook.list returns every endpoint registered under the calling key, including its delivery health (status, failureCount, lastDeliveredAt).

[
  {
    "id": "whk_01HG9X...",
    "url": "https://your-server.example.com/notpanel-webhook",
    "events": ["order.completed", "order.failed", "balance.low"],
    "status": "active",
    "failureCount": 0,
    "lastDeliveredAt": "2026-04-30T15:42:11.000Z",
    "createdAt": "2026-04-15T09:01:00.000Z"
  }
]

Remove a webhook

action=webhook.remove with id=whk_... removes the endpoint and revokes its signing secret. Existing in-flight deliveries that were already enqueued may still arrive; new events will not be sent.

Delivery payload

Every delivery is a POST to your registered URL with Content-Type: application/json. Headers include:

  • X-NotPanel-Signature — HMAC-SHA256 of the raw request body, hex-encoded.
  • X-NotPanel-Timestamp — Unix timestamp (seconds). Reject deliveries more than 5 minutes old to defeat replay attacks.
  • X-NotPanel-Event — Event name (mirrors event in the body).
{
  "event": "order.completed",
  "order_id": "ord_01HG9X7KJP4N...",
  "service_id": 1,
  "status": "completed",
  "charge": "0.50",
  "start_count": 1234,
  "remains": 0,
  "completed_at": "2026-04-30T15:42:11.000Z"
}

Verifying signatures

On receipt: compute HMAC-SHA256 of the raw body using the secret you received at registration, hex-encode, and compare to X-NotPanel-Signature using a constant-time comparison (e.g. Node's crypto.timingSafeEqual). Reject if they don't match — an attacker who guesses your URL but not your secret cannot forge a valid request.

Also reject deliveries where the timestamp is more than 5 minutes off your server clock — without this check, an attacker could replay an intercepted valid delivery later.

Delivery guarantees & retries

  • At-least-once. Successful 2xx responses are considered delivered. Anything else is retried with exponential backoff (1m → 5m → 30m → 2h → 12h) until the endpoint either succeeds or exhausts retries.
  • Disable threshold. After 20 consecutive failures, the endpoint is auto-disabled. Re-enable it from the dashboard or by re-registering.
  • Order is not guaranteed. Two events for the same order can arrive out of order under retry. Use X-NotPanel-Timestamp and the event payload to reconcile.