Webhooks
Webhooks मैनेज करें
एक सार्वजनिक https पता रजिस्टर करता है जो ऑर्डर या रिफिल बदलने पर हर कॉल के लिए एक हस्ताक्षरित इवेंट पाता है। Endpoint खाते के होते हैं, और हर एक का अपना सीक्रेट होता है।
https://notpanel.com/api/v3action=webhook.add- API की आवश्यक
- रेट लिमिट पार
- बॉडी:
application/x-www-form-urlencoded
पैरामीटर
हमेशा भेजा जाता है
keystringआवश्यक- आपकी API की।
actionstringआवश्यकwebhook.addलिटरल स्ट्रिंग "webhook.add" होना चाहिए।
एक्शन पैरामीटर
urlstring (URL)आवश्यक- वह सार्वजनिक https पता जो इवेंट पाता है।
eventsstring (CSV)वैकल्पिक- सब्सक्राइब करने के लिए कॉमा से अलग इवेंट प्रकार, 8 में से। सभी पाने के लिए इसे छोड़ दें।order.processing, order.in_progress, order.completed, order.partial, order.canceled, order.cancel_refused, refill.completed, refill.rejected
curl -X POST https://notpanel.com/api/v3 \
-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.partial,order.canceled"const res = await fetch("https://notpanel.com/api/v3", {
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.partial,order.canceled",
}),
});
const data = await res.json();
console.log(data);import requests
res = requests.post(
"https://notpanel.com/api/v3",
data={
"key": "YOUR_API_KEY",
"action": "webhook.add",
"url": "https://your-server.example.com/notpanel-webhook",
"events": "order.completed,order.partial,order.canceled",
},
)
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.partial,order.canceled',
]);
$response = file_get_contents('https://notpanel.com/api/v3', 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));
YOUR_API_KEY की जगह अपने API पेज की की डालें।
{
"id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
"url": "https://your-server.example.com/notpanel-webhook",
"events": ["order.completed", "order.partial", "order.canceled"],
"active": true,
"paused_at": null,
"created_at": "2026-10-03T09:00:00Z",
"status": "active",
"failureCount": 0,
"lastDeliveredAt": null,
"createdAt": "2026-10-03T09:00:00.000Z",
"secret": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"webhook_id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301"
}एरर
- 400
{"error":"Invalid webhook URL","error_code":"INVALID_WEBHOOK_URL","retryable":false}यह पता सार्वजनिक https पता नहीं है, या किसी निजी पते की ओर इशारा करता है।
- 400
{"error":"Invalid events: not_allowed","error_code":"INVALID_PARAMETER","retryable":false,"details":{"param":"events","rule":"not_allowed"}}कोई इवेंट नाम सूची में नहीं है। वर्तनी जाँचें: नाम
order.completedजैसे दिखते हैं। - 409
{"error":"Maximum 5 webhook endpoints per account","error_code":"WEBHOOK_LIMIT","retryable":false,"details":{"limit":5}}खाते में पहले से अधिकतम 5 endpoint हैं। पहले एक हटाएँ।
इवेंट
इनमें से किसी के भी लिए सब्सक्राइब करें। बदलाव सहेजे जाने पर हर एक अपनी अलग कॉल के रूप में भेजा जाता है, और वापस लिया गया बदलाव कुछ नहीं भेजता। Ping केवल webhook.test भेजता है।
इवेंट प्रकार
order.processing- ऑर्डर भेजा गया और डिलीवरी शुरू होने की प्रतीक्षा में है।
order.in_progress- डिलीवरी शुरू हो गई है।
order.completed- ऑर्डर पूरी तरह डिलीवर हो गया।
order.partial- ऑर्डर का एक हिस्सा डिलीवर हुआ और बाकी का रिफंड हुआ।
order.canceled- ऑर्डर रद्द और रिफंड हुआ। यह पहले के
order.refundedकी जगह लेता है। order.cancel_refused- रद्द करने का अनुरोध अस्वीकार हुआ। ऑर्डर चलता रहता है।
refill.completed- एक रिफिल हुआ। यह पहले के
order.refill_completedकी जगह लेता है। refill.rejected- एक रिफिल अस्वीकार हुआ। डेटा में कारण होता है।
ping- एक टेस्ट इवेंट, जो केवल
webhook.testभेजता है।
endpoint की सूची
action=webhook.list खाते के हर endpoint को उसके इवेंट, सक्रिय है या नहीं, और अगर रोका गया तो कब रोका गया, के साथ लौटाता है। सीक्रेट कभी नहीं लौटाया जाता। पुरानी keys status, failureCount, lastDeliveredAt और createdAt भी शामिल हैं।
https://notpanel.com/api/v3action=webhook.listkey=YOUR_API_KEY&action=webhook.list[
{
"id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
"url": "https://your-server.example.com/notpanel-webhook",
"events": ["order.completed", "order.partial", "order.canceled"],
"active": true,
"paused_at": null,
"created_at": "2026-10-03T09:00:00Z",
"status": "active",
"failureCount": 0,
"lastDeliveredAt": null,
"createdAt": "2026-10-03T09:00:00.000Z"
}
]टेस्ट इवेंट भेजें
action=webhook.test endpoint के लिए एक ping कतार में डालता है। इसका उपयोग अपने रिसीवर और हस्ताक्षर कोड की जाँच के लिए करें। जवाब में डिलीवरी आईडी होता है।
https://notpanel.com/api/v3action=webhook.testkey=YOUR_API_KEY&action=webhook.test&id=3f2504e0-4f89-41d3-9a0c-0305e82c3301{
"queued": true,
"delivery_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}endpoint हटाएँ
action=webhook.remove endpoint और उसका सीक्रेट हटा देता है। रास्ते में पड़ी डिलीवरी अब भी पहुँच सकती है; उसके लिए कुछ नया कतार में नहीं डाला जाता।
https://notpanel.com/api/v3action=webhook.removekey=YOUR_API_KEY&action=webhook.remove&id=3f2504e0-4f89-41d3-9a0c-0305e82c3301{
"removed": true,
"id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
"success": true
}डिलीवरी पेलोड
हर डिलीवरी Content-Type: application/json वाले एक POST में एक इवेंट होती है। बॉडी एक एनवेलप है, और data में ऑर्डर या रिफिल उसी रूप में होता है जैसा status और refill_status में। यह स्कीमा संस्करण 1 है।
एनवेलप
idstring- इवेंट का अनूठा आईडी। डुप्लिकेट अनदेखे करने के लिए इस्तेमाल करें।
typestring- इवेंट प्रकार।
schema_versioninteger- इस फ़ॉर्मेट का संस्करण। अभी यह 1 है।
created_atstring- इवेंट कब बनाया गया।
sequenceinteger- खाते के हर इवेंट के साथ बढ़ता है, ताकि आप इवेंट को क्रम में रख सकें।
dataobjectorderयाrefill, संबंधित रीड कार्रवाई के फ़ॉर्मेट में। ping के लिए खाली।
अनुरोध हेडर
X-Webhook-Signature- टाइमस्टैम्प, एक बिंदु और सटीक बॉडी का HMAC-SHA256, जो
sha256=के बाद हेक्स अंकों के रूप में लिखा जाता है। X-Webhook-Timestamp- इस प्रयास का सेकंड में Unix समय। रीट्राई को नया टाइमस्टैम्प और हस्ताक्षर मिलता है।
X-Webhook-Event- इवेंट प्रकार, बॉडी के
typeजैसा ही। X-Webhook-Delivery-Id- इस डिलीवरी का आईडी, उसके हर रीट्राई पर वही।
{
"id": "e1f0c9a2-3a8d-4a0e-9d8f-6c1b2a3d4e51",
"type": "order.completed",
"schema_version": 1,
"created_at": "2026-10-03T12:30:00Z",
"sequence": 1042,
"data": {
"order": {
"charge": "1.20",
"start_count": "3572",
"status": "Completed",
"remains": "0",
"currency": "USD",
"order": 1234567,
"status_key": "completed",
"refund": "0.00",
"reason_code": null,
"reason": null,
"can_cancel": false,
"cancel_available": false,
"cancel_state": null,
"can_refill": false,
"refill_available_at": "2026-10-03T18:35:00Z",
"updated_at": "2026-10-03T12:35:00Z"
}
}
}{
"id": "e1f0c9a2-3a8d-4a0e-9d8f-6c1b2a3d4e52",
"type": "order.partial",
"schema_version": 1,
"created_at": "2026-10-03T12:30:00Z",
"sequence": 1043,
"data": {
"order": {
"charge": "0.87",
"start_count": "3572",
"status": "Partial",
"remains": "157",
"currency": "USD",
"order": 1234568,
"status_key": "partial",
"refund": "0.13",
"reason_code": "partially_delivered",
"reason": "Part of the order was delivered. The rest was refunded.",
"can_cancel": false,
"cancel_available": false,
"cancel_state": null,
"can_refill": true,
"refill_available_at": null,
"updated_at": "2026-10-03T12:00:00Z"
}
}
}{
"id": "e1f0c9a2-3a8d-4a0e-9d8f-6c1b2a3d4e53",
"type": "order.canceled",
"schema_version": 1,
"created_at": "2026-10-03T12:30:00Z",
"sequence": 1044,
"data": {
"order": {
"charge": "0.00",
"start_count": "0",
"status": "Canceled",
"remains": "1000",
"currency": "USD",
"order": 1234569,
"status_key": "cancelled",
"refund": "1.20",
"reason_code": "customer_canceled",
"reason": "You canceled this order. The charge was refunded.",
"can_cancel": false,
"cancel_available": false,
"cancel_state": null,
"can_refill": false,
"refill_available_at": null,
"updated_at": "2026-10-03T10:00:09Z"
}
}
}{
"id": "e1f0c9a2-3a8d-4a0e-9d8f-6c1b2a3d4e56",
"type": "refill.rejected",
"schema_version": 1,
"created_at": "2026-10-03T12:30:00Z",
"sequence": 1047,
"data": {
"refill": {
"refill": 5502,
"status": "Rejected",
"status_key": "rejected",
"order": 1234568,
"reason_code": "refill_too_early",
"reason": "This order cannot be refilled yet. Try again later.",
"created_at": "2026-10-03T12:40:00Z",
"updated_at": "2026-10-03T12:41:00Z"
}
}
}हस्ताक्षर सत्यापित करना
X-Webhook-Timestamp और बॉडी के सटीक बाइट पढ़ें। endpoint के सीक्रेट से <timestamp>.<body bytes> पर HMAC-SHA256 निकालें, उसे sha256=<hex> के रूप में लिखें, और X-Webhook-Signature से स्थिर समय में तुलना करें। रीट्राई का इवेंट आईडी वही रहता है पर टाइमस्टैम्प और हस्ताक्षर नए होते हैं।
रॉ बॉडी और हेडर पढ़ें
हस्ताक्षर निकालें और तुलना करें
जिन इवेंट को आप पहले संभाल चुके हैं उन्हें छोड़ दें
import { createHmac, timingSafeEqual } from "node:crypto";
/**
* @param {{rawBody: unknown, signatureHeader: unknown, timestampHeader: unknown,
* secret: unknown, nowSeconds?: number, toleranceSeconds?: number}} input
*/
function verifyNotPanelBody({
rawBody,
signatureHeader,
timestampHeader,
secret,
nowSeconds = Math.floor(Date.now() / 1000),
toleranceSeconds = 300, // Example receiver policy, not a NotPanel rule.
}) {
if (!Buffer.isBuffer(rawBody)) return false;
if (typeof secret !== "string" || secret.length === 0) return false;
if (typeof signatureHeader !== "string") return false;
if (typeof timestampHeader !== "string") return false;
if (!/^(0|[1-9]\d{0,15})$/.test(timestampHeader)) return false;
if (!Number.isSafeInteger(nowSeconds)) return false;
if (!Number.isSafeInteger(toleranceSeconds) || toleranceSeconds < 0) {
return false;
}
const timestamp = Number(timestampHeader);
if (!Number.isSafeInteger(timestamp)) return false;
if (Math.abs(nowSeconds - timestamp) > toleranceSeconds) return false;
const match = /^sha256=([0-9a-f]{64})$/i.exec(signatureHeader);
if (!match?.[1]) return false;
const expected = createHmac("sha256", secret)
.update(timestampHeader, "utf8")
.update(".", "utf8")
.update(rawBody)
.digest();
const received = Buffer.from(match[1], "hex");
return timingSafeEqual(expected, received);
}एक हल किया हुआ उदाहरण
इसे अपने कोड की जाँच के लिए इस्तेमाल करें। हस्ताक्षर में टाइमस्टैम्प, एक बिंदु और बॉडी ठीक वैसी ही शामिल है जैसी दिखाई गई है, बिना अतिरिक्त स्पेस या नई लाइन के।
secret = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
timestamp = 1790000000
body = {"id":"e1f0c9a2-3a8d-4a0e-9d8f-6c1b2a3d4e51","type":"order.completed","schema_version":1,"created_at":"2026-10-03T12:30:00Z","sequence":1042,"data":{"order":{"charge":"1.20","start_count":"3572","status":"Completed","remains":"0","currency":"USD","order":1234567,"status_key":"completed","refund":"0.00","reason_code":null,"reason":null,"can_cancel":false,"cancel_available":false,"cancel_state":null,"can_refill":false,"refill_available_at":"2026-10-03T18:35:00Z","updated_at":"2026-10-03T12:35:00Z"}}}X-Webhook-Signature: sha256=6e8e90578f566d2e14b5c3914eba7e378c4107a5a4d4ddd52c747791f2264fffडिलीवरी और रीट्राई
कम से कम एक बार।
10 सेकंड के भीतर कोई भी 2xx जवाब डिलीवर माना जाता है। रीडायरेक्ट का पालन नहीं किया जाता, और आपके जवाब के केवल पहले 4096 बाइट पढ़े जाते हैं। वही इवेंट एक से अधिक बार आ सकता है, इसलिए जिस इवेंट आईडी को आप पहले संभाल चुके हैं उसे अनदेखा करें।
एक दिन में आठ प्रयास।
कुल 8 प्रयास: पहला तुरंत, फिर इवेंट के 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h बाद। हर प्रयास में वही इवेंट आईडी और डिलीवरी आईडी रहता है।
endpoint खुद रुक जाते हैं।
लगातार 50 विफलताओं के बाद, या 3 दिन बिना सफलता के, endpoint रोक दिया जाता है और खाते के मालिक को बताया जाता है। अपना रिसीवर ठीक करें और endpoint फिर जोड़ें।
क्रम की गारंटी नहीं है।
इवेंट बेक्रम आ सकते हैं। उन्हें क्रम में रखने के लिए
sequenceइस्तेमाल करें, हर इवेंट आईडी को एक बार संभालें, और मौजूदा स्थिति चाहिए तोaction=statusपढ़ें।