मुख्य कंटेंट पर जाएँ
Sursa Developers

वेबहुक पाना

वेबहुक आपके सिस्टम को Sursa में हुए बदलाव के बारे में उसी समय बताता है: कोई कार्ड प्रकाशित, सत्यापित या आर्काइव हुआ, किसी सवाल का जवाब नहीं मिला। Sursa इवेंट के साथ एक HTTPS POST उस पते पर भेजता है, जो आप रजिस्टर करते हैं। यह गाइड ऐसा प्राप्तकर्ता बनाती है, जो रीट्राई, बदले हुए क्रम और डुप्लिकेट के बावजूद सही काम करता है।

हर इवेंट का क्या मतलब है और उसमें क्या होता है, यह वेबहुक का परिचय और API रेफ़रेंस के इवेंट कैटलॉग में देखें।

1. वेबहुक रजिस्टर करें

एडमिन Sursa में Administration → Integrations → Webhooks and API (एडमिनिस्ट्रेशन → इंटीग्रेशन → वेबहुक और API) में वेबहुक रजिस्टर करता है, या admin रोल और webhooks:manage स्कोप वाली कुंजी के साथ POST /v1/webhooks से:

curl -X POST https://api.sursa.com/v1/webhooks \
  -H "Authorization: Bearer $SURSA_ADMIN_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/sursa",
    "description": "Sync verified cards to the help center",
    "events": ["card.published", "card.updated", "card.verified", "card.archived"]
  }'

रिस्पॉन्स में साइनिंग सीक्रेट secret में होता है, जो whsec_ से शुरू होता है। यह सिर्फ़ एक बार दिखाया जाता है। इसे अपने प्राप्तकर्ता के साथ रखें, उदाहरण के लिए एनवायरनमेंट वेरिएबल SURSA_WEBHOOK_SECRET में।

पते में https होना चाहिए और वह पब्लिक IP पतों पर रिज़ॉल्व होना चाहिए: प्राइवेट, लूपबैक और दूसरे आरक्षित नेटवर्क के पते रजिस्टर करते समय और हर डिलीवरी पर अस्वीकार किए जाते हैं। अपने कंप्यूटर पर डेवलप करने के लिए अपने लोकल प्राप्तकर्ता को किसी टनलिंग सर्विस के ज़रिए उपलब्ध कराएँ, जो उसे पब्लिक HTTPS पता देती है।

POST /v1/webhooks/{webhook_id}/test से कभी भी टेस्ट इवेंट भेजें। रिस्पॉन्स दिखाता है कि आपके प्राप्तकर्ता ने क्या जवाब दिया।

2. डिलीवरी कैसी दिखती है

POST /sursa HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
webhook-id: evt_0123456789abcdef0123456789abcdef
webhook-timestamp: 1791194400
webhook-signature: v1,l1LLl/aEl4dQq7Geb383KbyNeZgs7VZpiw6iuoJgunI=
X-KB-Signature: t=1791194400,v1=82aa6b27378d89d7d880d17c3fe96268598443ae0e64d059c0c41e94e5d3ffbe
X-KB-Event-Id: evt_0123456789abcdef0123456789abcdef
X-KB-Event: card.verified
X-KB-Delivery: 99812
X-KB-Attempt: 1
X-KB-Sequence: 1042

{"id":"evt_0123456789abcdef0123456789abcdef","type":"card.verified","created_at":"2026-10-05T10:00:00Z","org_id":1,"api_version":"1.60.0","sequence":1042,"object":{"type":"card","id":"c1"},"payload":"full","data":{"card":{"id":"c1"}},"test":false}
हेडरमतलब
webhook-idइवेंट की ID। हर रीट्राई में एक ही। डुप्लिकेट हटाने के लिए इसे इस्तेमाल करें।
webhook-timestampइस प्रयास पर कब साइन किया गया, Unix सेकंड में।
webhook-signatureStandard Webhooks फ़ॉर्मेट में सिग्नेचर।
X-KB-Signatureवही डिलीवरी, Sursa के पुराने फ़ॉर्मेट में साइन की हुई।
X-KB-Eventइवेंट टाइप।
X-KB-Attemptप्रयास का नंबर, 1 से।
X-KB-Sequenceवेबहुक के क्रम में इस डिलीवरी की स्थिति।

हेडर के नामों में बड़े-छोटे अक्षरों का फ़र्क़ नहीं पड़ता; आपका फ़्रेमवर्क उन्हें X-Kb-Signature या x-kb-signature के रूप में दिखा सकता है।

बॉडी इवेंट है। उसका data टाइप पर निर्भर करता है। thin पेलोड पर सेट वेबहुक को इवेंट data के बिना मिलता है; इसके बजाय ऑब्जेक्ट को अपनी कुंजी से API के ज़रिए पढ़ें।

3. सिग्नेचर जाँचें

हर डिलीवरी पर भरोसा करने से पहले उसका सिग्नेचर जाँचें। दोनों फ़ॉर्मेट बॉडी के हूबहू बाइट पर साइन करते हैं, इसलिए JSON पार्स करने से पहले रॉ बॉडी पढ़ें: JSON को फिर से सीरियलाइज़ करने से बाइट बदल जाते हैं और जाँच टूट जाती है।

फ़ॉर्मेटसाइन की गई स्ट्रिंगकुंजीसिग्नेचर
Standard Webhooks (webhook-signature)<webhook-id>.<webhook-timestamp>.<body>सीक्रेट का whsec_ के बाद वाला हिस्सा, base64 से डिकोड किया हुआv1, + HMAC-SHA256 का base64
पुराना फ़ॉर्मेट (X-KB-Signature)<t>.<body>पूरी सीक्रेट स्ट्रिंग, टेक्स्ट के रूप मेंv1= + HMAC-SHA256 का hex

इनमें से कोई एक जाँचना काफ़ी है। Standard Webhooks हेडर को प्राथमिकता दें: यही जाँच कई तैयार लाइब्रेरी में उपलब्ध है। सीक्रेट रोटेट होने के दौरान हर हेडर में हर मान्य सीक्रेट के लिए एक सिग्नेचर होता है (webhook-signature में स्पेस से अलग, X-KB-Signature में दोहराया गया v1=)। डिलीवरी मान्य है, अगर उनमें से कोई भी मेल खाता है।

ऐसा टाइमस्टैम्प भी अस्वीकार करें, जो आपकी घड़ी से 5 मिनट से ज़्यादा दूर हो। इससे कोई रिकॉर्ड की गई डिलीवरी बाद में फिर से नहीं भेज सकता।

# Check a saved delivery by hand: body.json holds the raw body, the values come from its headers.
SECRET="whsec_…"; WEBHOOK_ID="evt_…"; TIMESTAMP="1791194400"

# Standard Webhooks: compare with the value after "v1," in webhook-signature
KEY=$(printf '%s' "${SECRET#whsec_}" | tr '_-' '/+')
while [ $(( ${#KEY} % 4 )) -ne 0 ]; do KEY="$KEY="; done
KEY_HEX=$(printf '%s' "$KEY" | openssl base64 -d -A | xxd -p | tr -d '\n')
{ printf '%s.%s.' "$WEBHOOK_ID" "$TIMESTAMP"; cat body.json; } \
  | openssl dgst -sha256 -mac HMAC -macopt "hexkey:$KEY_HEX" -binary | openssl base64 -A

# Earlier format: compare with the value after "v1=" in X-KB-Signature
{ printf '%s.' "$TIMESTAMP"; cat body.json; } | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1

अपना जाँच कोड टेस्ट करें

असली डिलीवरी से जोड़ने से पहले अपना कोड इन मानों पर चलाएँ। घड़ी को टाइमस्टैम्प पर सेट करने पर दोनों जाँच पास होनी चाहिए, और बॉडी का एक बाइट बदलने पर असफल होनी चाहिए।

इनपुटमान
सीक्रेटwhsec_test_secret_0123456789abcdef
webhook-idevt_0123456789abcdef0123456789abcdef
webhook-timestamp और t1791194400
बॉडी{"id":"evt_0123456789abcdef0123456789abcdef","type":"card.verified","created_at":"2026-10-05T10:00:00Z","org_id":1,"data":{"card":{"id":"c1"}}}
webhook-signaturev1,+xxWSXH6weQJ/MVg4+N78a1Dj4y/AQ4p94+PRNzpECg=
X-KB-Signaturet=1791194400,v1=ce26a19cb63cd3ae620217bf7688e1df1044fa7d59d9ae148d215f0292b768dc

4. जल्दी जवाब दें, काम बाद में करें

Sursa आपके जवाब के लिए 10 सेकंड तक इंतज़ार करता है। कोई भी 2xx स्टेटस मतलब डिलीवरी सफल रही; बाक़ी कुछ भी, रीडायरेक्ट, टाइमआउट या बंद कनेक्शन समेत, मतलब वह असफल रही और उसका रीट्राई होगा।

इसलिए प्राप्तकर्ता को सिर्फ़ जाँच करनी चाहिए, इवेंट दर्ज करना चाहिए और जवाब देना चाहिए। असली काम बाद में, अपनी कतार से करें।

// Node.js 20+, no framework. verifyStandard and verifyLegacy come from step 3.
import http from "node:http";

const SECRET = process.env.SURSA_WEBHOOK_SECRET;

http
  .createServer(async (req, res) => {
    const parts = [];
    for await (const part of req) parts.push(part);
    const body = Buffer.concat(parts); // the raw bytes, before any JSON parsing

    const valid = req.headers["webhook-signature"]
      ? verifyStandard(SECRET, req.headers, body)
      : verifyLegacy(SECRET, req.headers["x-kb-signature"], body);
    if (!valid) {
      res.writeHead(400).end();
      return;
    }

    const event = JSON.parse(body);
    await saveEventOnce(event); // insert into your table or queue, keyed by event.id; ignore a duplicate
    res.writeHead(204).end();
  })
  .listen(3000);

2xx जवाब सिर्फ़ तब दें, जब इवेंट सुरक्षित रूप से सेव हो जाए। अगर आप पहले जवाब देते हैं और फिर क्रैश हो जाते हैं, तो इवेंट आपके लिए खो जाता है, क्योंकि Sursa उसे पहले ही डिलीवर हुआ मान चुका है।

5. रीट्राई

असफल डिलीवरी का बढ़ते अंतराल पर रीट्राई होता है: लगभग 1 मिनट, 5 मिनट, 30 मिनट, 2 घंटे, 5 घंटे, 10 घंटे, 24 घंटे और 24 घंटे बाद, लगभग तीन दिनों में नौ प्रयास। हर अंतराल 20 % तक बदलता है, ताकि कई वेबहुक के रीट्राई एक ही पल में न पहुँचें।

  • Retry-After के साथ 429 या 503 जवाब अगले प्रयास को कम से कम उतने समय बाद ले जाता है, ज़्यादा से ज़्यादा 24 घंटे बाद तक। इसे तब इस्तेमाल करें, जब आपका सिस्टम ओवरलोड हो या मेंटेनेंस में हो।
  • 410 Gone जवाब वेबहुक को बंद कर देता है: इसे सिर्फ़ तब भेजें, जब आप उस पते पर आगे कोई डिलीवरी नहीं चाहते।
  • लगातार 10 असफल प्रयासों के बाद वेबहुक पॉज़ कर दिया जाता है, और संगठन के एडमिन को सूचना दी जाती है। इवेंट जमा होते रहते हैं, और जब वेबहुक फिर से शुरू किया जाता है, एडमिन इंटरफ़ेस में या POST /v1/webhooks/{webhook_id}/resume से, तो वे क्रम से डिलीवर होते हैं।
  • आख़िरी प्रयास के बाद डिलीवरी exhausted चिह्नित होती है। आप उसे हाथ से फिर से भेज सकते हैं।

वेबहुक की डिलीवरी, हर प्रयास, आपके जवाब के कोड और उसके समय के साथ, GET /v1/webhooks/{webhook_id}/deliveries में और एडमिन इंटरफ़ेस में दिखती हैं। POST /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver किसी एक डिलीवरी को फिर से भेजता है। दोबारा डिलीवरी का webhook-id वही होता है।

6. क्रम और डुप्लिकेट

  • क्रम। एक वेबहुक की डिलीवरी sequence के क्रम में सख़्ती से एक के बाद एक जाती हैं। जब किसी डिलीवरी का रीट्राई हो रहा होता है, तो बाद वाली इंतज़ार करती हैं। अलग-अलग वेबहुक एक-दूसरे का इंतज़ार नहीं करते।
  • डुप्लिकेट। एक ही इवेंट एक से ज़्यादा बार आ सकता है, उदाहरण के लिए, जब आपका जवाब वापसी में खो गया हो। प्रोसेस किए गए हर इवेंट का webhook-id (यह बॉडी में id भी है) सेव करें, और जो पहले देख चुके हैं, उसे छोड़ दें।
  • स्थितियों के हिसाब से सोचें। एक ही ऑब्जेक्ट के बदलावों के लिए created_at या sequence की तुलना अपने सेव किए डेटा से करें, और उस इवेंट को नज़रअंदाज़ करें, जो आपके पास मौजूद स्थिति से पुराना है। संदेह हो, तो मौजूदा ऑब्जेक्ट API से पढ़ें: इवेंट बताता है कि कुछ बदला, API बताता है कि अब वह क्या है।

7. इवेंट फ़ीड से मिलान करें

हर इवेंट 30 दिनों तक इवेंट फ़ीड में भी रखा जाता है, चाहे किसी वेबहुक ने उसे सब्सक्राइब किया हो या नहीं। अगर आपका प्राप्तकर्ता रीट्राई की अवधि से ज़्यादा समय तक बंद रहा, या आप जाँचना चाहते हैं कि कुछ छूटा नहीं, तो admin रोल और webhooks:manage स्कोप वाली कुंजी से फ़ीड पढ़ें:

curl -G https://api.sursa.com/v1/webhook-events \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  --data-urlencode "type=card.*" \
  --data-urlencode "created_since=2026-10-20T00:00:00Z" \
  --data-urlencode "limit=200"

फ़ीड में इवेंट का id और ढाँचा डिलीवरी जैसा ही होता है, डिलीवरी के फ़ील्ड sequence, payload और test के बिना। चूँकि आपका प्राप्तकर्ता पहले देखे गए इवेंट छोड़ देता है, आप मिलान जितनी बार चाहें चला सकते हैं, उदाहरण के लिए हर घंटे में एक बार।

GET /v1/webhook-events/{event_id} किसी एक इवेंट को उसकी ID से पढ़ता है।

8. सीक्रेट रोटेट करें

POST /v1/webhooks/{webhook_id}/rotate-secret नया सीक्रेट जारी करता है और पिछले सीक्रेट को ओवरलैप अवधि तक मान्य रखता है, डिफ़ॉल्ट रूप से 24 घंटे और ज़्यादा से ज़्यादा 7 दिन। ओवरलैप अवधि में हर डिलीवरी दोनों सीक्रेट से साइन होती है।

  1. रोटेट करें, और नया सीक्रेट अपने प्राप्तकर्ता में पुराने के साथ रखें।
  2. डिलीवरी स्वीकार करें, अगर वह दोनों में से किसी भी सीक्रेट से जाँच में सही निकले।
  3. ओवरलैप अवधि के बाद पुराना सीक्रेट हटा दें।

अगर पुराना सीक्रेट लीक हो गया है, तो ओवरलैप अवधि तुरंत ख़त्म करें: POST /v1/webhooks/{webhook_id}/revoke-previous-secret से, या "overlap_hours": 0 के साथ रोटेट करके।

चेकलिस्ट

  • रॉ बॉडी पर किसी एक सिग्नेचर की जाँच करें, और टाइमस्टैम्प जाँचें।
  • इवेंट को webhook-id के साथ सेव करें, फिर 10 सेकंड के अंदर 2xx जवाब दें।
  • डुप्लिकेट और पुराने इवेंट छोड़ दें; जब ज़रूरी हो, मौजूदा ऑब्जेक्ट API से पढ़ें।
  • जब आपको Sursa से रफ़्तार कम करवानी हो, तो Retry-After के साथ 429 या 503 जवाब दें; ग़लती से कभी 410 जवाब न दें।
  • अनजान इवेंट टाइप और फ़ील्ड छोड़ दें।
  • शेड्यूल के अनुसार इवेंट फ़ीड से मिलान करें।