ମୁଖ୍ୟ ବିଷୟବସ୍ତୁକୁ ଛାଡ଼ନ୍ତୁ
ଶିଖନ୍ତୁଗାଇଡ୍ଏଜେଣ୍ଟ ଦେୟଗୁଡିକୁ ଓେବ୍‌ହୁକ୍‌ସହିତ ହାଣ୍ଡଲ୍ କରନ୍ତୁ
ଗାଇଡ୍

developer ମାନେ ସବୁଠାରୁ ଅଧିକ ପଚାରୁଥିବା webhook pattern।

15 ମିନିଟ୍
ସଂକ୍ଷିପ୍ତ ଉତ୍ତର

ଏକ dependable webhook handler କ୍ରମରେ ଚାରିଟି କାମ କରେ: raw body ବିରୁଦ୍ଧରେ signature verify କରେ (Node ରେ webhooks.verify, ଅନ୍ୟ ସ୍ଥାନରେ documented HMAC), event id ଅନୁସାରେ dedupe କରେ, work କୁ background queue ରେ enqueue କରେ, ଏବଂ 200 ଫେରାଏ। ଦୀର୍ଘ ସମୟ ନେଇଥିବା କାମ worker ରେ ହୁଏ, queue layer ରେ retries ଏବଂ idempotency ସହିତ। failure event ନଥିବାରୁ, ଏକ scheduled sweep payment ଅପେକ୍ଷାରେ ଅଟକିଥିବା jobs କୁ timeout କରେ ଏବଂ ସେମାନଙ୍କୁ reconcile କରେ।

ପୂର୍ବାବଶ୍ୟକତା

ଆପଣ ଆରମ୍ଭ କରିବା ପୂର୍ବରୁ।

  • ଏକ କାମ କରୁଥିବା ଏଜେଣ୍ଟ ପ୍ରୋଫାଇଲ୍ ଏବଂ ତୁମର ଡ୍ୟାସ୍‌ବୋର୍ଡରୁ ସାଇନିଂ ସିକ୍ରେଟ୍ (ସେଟିଂସ୍ - ହେବୋକ୍ସ)।
  • ଏକ ଓବ୍‌ ଫ୍ରେମ୍‌ୱର୍କ ଯାହାର କଚ୍ଚା-ଶରୀର ଆକ୍ସେସ୍ ଅଛି - Express with express.raw, FastAPI, Flask, ଇତ୍ୟାଦି। ଆଟୋ-ପାର୍ସିଂ JSON ମିଡଲ୍‌ୱେୟାର୍ ସିଗ୍ନେଚର୍ ଭେରିଫିକେସନ୍‌କୁ ଭଙ୍ଗ କରେ।
  • ଏକ job queue: BullMQ (Node) କିମ୍ବା Celery/arq (Python)। webhook ଶୀଘ୍ର 200 ଫେରାଏ ଏବଂ queue ଧୀର କାମ କରେ।
  • ଏକ upsert primitive ସହିତ database (Postgres ଚାଲେ; short-lived dedupe ପାଇଁ Redis SET NX ମଧ୍ୟ ଚାଲେ)।
  • ଏକ public HTTPS endpoint - development ରେ, ngrok କିମ୍ବା deploy preview। sender private URLs କୁ deliver କରିବ ନାହିଁ।
ପଦକ୍ଷେପ 1 ର 4

ସହି ସତ୍ୟାପନ କରନ୍ତୁ।

The signature is HMAC-SHA256 over {t}.{rawBody} with your webhook secret, hex-encoded, in the X-Blockchain0x-Signature header (t=<unix>,v1=<hex>), inside a 5-minute replay window. In Node, webhooks.verify from @blockchain0x/node does it and returns a discriminated union; in other languages compute the same HMAC and compare in constant time. Raw-body access matters: if the bytes you sign locally differ from the bytes that arrived, it fails.

TypeScript
import express from "express";
import { webhooks } from "@blockchain0x/node";

const app = express();
// Raw body so the HMAC matches the exact bytes on the wire.
app.use(express.raw({ type: "application/json" }));

app.post("/webhooks/payment", (req, res) => {
  const result = webhooks.verify({
    headers: req.headers,
    rawBody: req.body, // Buffer, raw bytes
    secret: process.env.BLOCKCHAIN0X_WEBHOOK_SECRET!,
  });
  // Discriminated union: branch on ok, no try/catch.
  if (!result.ok) return res.status(400).json({ code: result.code });
  // result.eventType / result.eventId are now set.
  handleEvent(result);
  res.status(200).send("ok");
});
Python
import hmac, hashlib, os, time
from flask import request

SECRET = os.environ["BLOCKCHAIN0X_WEBHOOK_SECRET"].encode()

# In Node, webhooks.verify does this. In Python, verify by hand against the
# documented algorithm: HMAC-SHA256 over "{t}.{rawBody}", 300s replay window.
def verify_signature(raw_body: bytes) -> bool:
    sig = request.headers.get("X-Blockchain0x-Signature", "")
    ts = request.headers.get("X-Blockchain0x-Timestamp", "")
    parts = dict(p.split("=", 1) for p in sig.split(",") if "=" in p)
    t, v1 = parts.get("t", ts), parts.get("v1", sig)
    want = hmac.new(SECRET, t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(want, v1) and abs(time.time() - int(t)) <= 300
ପଦକ୍ଷେପ 2 ର 4

ହ୍ୟାଣ୍ଡଲର୍କୁ ଇଡେମ୍ପୋଟେଣ୍ଟ କରନ୍ତୁ।

Webhooks retry on any non-2xx response, and the same event will arrive multiple times under load even when nothing has gone wrong. Dedupe on the event's id using a database upsert. If the row already exists, skip; if it does not, insert and proceed. Postgres makes this a single statement.

TypeScript
// Pseudocode for a Postgres-backed dedupe table. Replace with your DB of choice.
async function processEventOnce(eventId: string, body: object) {
  // INSERT ... ON CONFLICT DO NOTHING returns rowCount === 0 on duplicate.
  const inserted = await db.query(
    "INSERT INTO webhook_events(id) VALUES ($1) ON CONFLICT DO NOTHING",
    [eventId],
  );
  if (inserted.rowCount === 0) return;          // Already processed.
  await handleEvent(body);
}
Python
async def process_event_once(event_id: str, body: dict):
    # INSERT ... ON CONFLICT DO NOTHING returns 0 rows on duplicate.
    inserted = await db.execute(
        "INSERT INTO webhook_events (id) VALUES ($1) ON CONFLICT DO NOTHING",
        event_id,
    )
    if inserted == "INSERT 0 0":   # asyncpg-style status
        return                     # Already processed.
    await handle_event(body)
ପଦକ୍ଷେପ 3 ର 4

ଏନ୍କ୍ୟୁ କରନ୍ତୁ ଏବଂ 200 ତ୍ୱରିତ ଫେରାଇବେ।

webhook endpoint ଗୋଟିଏ second ମଧ୍ୟରେ respond କରିବା ଉଚିତ। ତାହାଠାରୁ ଧୀର ହେଲେ timeout ଏବଂ retry ଆମନ୍ତ୍ରଣ ମିଳେ। pattern ହେଉଛି: verify, enqueue, respond। queue worker ଭିତରେ retries, exponential backoff, ଏବଂ ନିଜ idempotency ସହିତ ପ୍ରକୃତ delivery ଚଳାଏ। BullMQ ଏବଂ Celery ଉଭୟ per-job ID ସମର୍ଥନ କରେ, ଯାହା ସେଇ event କୁ ଅଚେତନରେ ପୁନଃ enqueue ହେବାରୁ ବଞ୍ଚାଏ।

TypeScript (BullMQ)
// Express handler: verify, enqueue, return 200 fast.
import { Queue } from "bullmq";
import { webhooks } from "@blockchain0x/node";

const paymentQueue = new Queue("payments");

app.post(
  "/webhooks/payment",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const result = webhooks.verify({
      headers: req.headers,
      rawBody: req.body,
      secret: process.env.BLOCKCHAIN0X_WEBHOOK_SECRET!,
    });
    if (!result.ok) return res.status(400).json({ code: result.code });
    await paymentQueue.add(result.eventType, { raw: req.body.toString() }, {
      jobId: result.eventId,            // Idempotency key.
      removeOnComplete: true,
      attempts: 5,
      backoff: { type: "exponential", delay: 1000 },
    });
    res.status(200).send("ok");
  },
);

// Worker file:
import { Worker } from "bullmq";
new Worker("payments", async (job) => {
  await handleEvent(job.data);
});
Python (Celery)
# Flask handler enqueues to Celery (or arq) and returns 200 quickly.
from celery import Celery
from flask import request

celery = Celery("payments", broker=os.environ["REDIS_URL"])

@celery.task(bind=True, max_retries=5)
def handle_payment_event(self, event_type, raw):
    try:
        process_event_once(event_type, raw)
    except Exception as exc:
        raise self.retry(exc=exc, countdown=2 ** self.request.retries)

@app.post("/webhooks/payment")
def webhook():
    raw = request.get_data()
    if not verify_signature(raw):
        abort(401)
    event_id = request.headers.get("X-Blockchain0x-Event-Id", "")
    event_type = request.headers.get("X-Blockchain0x-Event-Type", "")
    handle_payment_event.apply_async(args=[event_type, raw.decode()], task_id=event_id)
    return "ok", 200

arq Python ପାର୍ଶ୍ୱରେ ସେହି ଆକୃତିକୁ ଅନୁସରଣ କରେ - ଏକ ନିର୍ଣ୍ଣୟଶୀଳ ଜବ୍ id ସହିତ କାମକୁ ରେଜିଷ୍ଟର କରନ୍ତୁ ଏବଂ କ୍ୟୁକୁ ପୁନରାବୃତ୍ତି ସମ୍ଭାଳିବାକୁ ଦିଅନ୍ତୁ। ମୁଖ୍ୟ ସୀମାବଧ୍ଧତା ହେଉଛି ଯେ ଏନ୍କ୍ୟୁ ନିଜେ ଦ୍ରୁତ ହେବା ଦରକାର (Redis କୁ ଏକ ଏକାଧିକ ଯାତ୍ରା); ଦୂରସଂଯୋଗ କଲେ ୱେବହୁକ୍ କୁ ବ୍ଲକ୍ କରନ୍ତୁ ନାହିଁ।

ପଦକ୍ଷେପ 4 ର 4

ଜାହା କେବେ ଲାଣ୍ଡ୍ ହୁଏ ନାହିଁ ତାହାର ଭୁଗତାନକୁ ହ୍ୟାଣ୍ଡଲ୍ କରନ୍ତୁ।

failure webhook ନାହିଁ - ଯଦି buyer ଛାଡିଦିଏ, କୌଣସି event ଆସେ ନାହିଁ, ଏବଂ agent 'awaiting_payment' ଭିତରେ ଅଟକି ରହିଯାଏ। ସେହିପରି ନିଜେ detect କରନ୍ତୁ: ଅତ୍ୟଧିକ ସମୟ ଅପେକ୍ଷା କରିଥିବା jobs ଉପରେ scheduled sweep ଚଳାନ୍ତୁ, ଯଦି ସତରେ settle ହୋଇଥାଏ ବୋଲି chain ସହିତ transactions.get ଦ୍ୱାରା reconcile କରନ୍ତୁ, ତାପରେ held resources release କରନ୍ତୁ, job କୁ terminal unpaid state କୁ move କରନ୍ତୁ, ଏବଂ (ଯଦି ଉପଯୁକ୍ତ ହୋଇଥାଏ) outcome କୁ user କୁ surface କରନ୍ତୁ।

TypeScript
async function sweepStaleAwaitingPayment() {
  for (const job of await findJobsAwaitingPaymentOlderThan("1h")) {
    // Reconcile against the chain before giving up.
    const tx = job.txHash ? await client.transactions.get(job.txHash) : null;
    if (tx) { markJobPaid(job.id); continue; }   // It actually settled.

    // 1. Release any held resources tied to the job.
    releaseHeldResources(job.id);
    // 2. Move it out of 'awaiting_payment' into a terminal 'unpaid' state.
    markJobUnpaid(job.id);
    // 3. (Optional) Notify the user, with a fresh payment link.
    notifyUser(job.userId, { template: "agent_payment_unpaid", jobId: job.id });
  }
}
Python
# Run on a schedule - there is no failure webhook to wait for.
def sweep_stale_awaiting_payment():
    for job in find_jobs_awaiting_payment_older_than("1h"):
        tx = client.transactions.get(job["tx_hash"]) if job.get("tx_hash") else None
        if tx:
            mark_job_paid(job["id"])   # It actually settled.
            continue

        release_held_resources(job["id"])
        mark_job_unpaid(job["id"])
        notify_user(job["user_id"], template="agent_payment_unpaid", job_id=job["id"])
ସାଧାରଣ ବିପଦ

ଘଟଣାଗୁଡିକୁ ହରାଇବା କିମ୍ବା ଡୁପ୍ଲିକେଟ୍ କରାଉଥିବା ପାଞ୍ଚ ଭୁଲ।

ସହିକୁ ଯାଞ୍ଚ କରିବା ପୂର୍ବରୁ ଦେହକୁ ପାର୍ସ କରିବା

HMAC କୁ ପଠାଇଥିବା ବ୍ୟକ୍ତିଙ୍କ ଦ୍ୱାରା ସାଇନ୍ କରାଯାଇଥିବା କଚା ବାଇଟ୍ ଉପରେ ହିସାବ କରାଯିବା ଆବଶ୍ୟକ। ଯଦି ଆପଣଙ୍କର ଫ୍ରେମ୍‌ୱର୍କ ଆପଣଙ୍କର ହ୍ୟାଣ୍ଡଲର ଚାଲିବା ପୂର୍ବରୁ JSON କୁ ସ୍ୱୟଂ-ପାର୍ସ କରେ, ଆପଣ ଯାହାକୁ ସ୍ଥାନୀୟ ଭାବେ ସାଇନ୍ କରନ୍ତି ସେଗୁଡିକ ପଠାଇଥିବା ବ୍ୟକ୍ତିଙ୍କ ଦ୍ୱାରା ସାଇନ୍ କରାଯାଇଥିବା ବାଇଟ୍ ସହିତ ମିଳିବ ନାହିଁ (ବିଭିନ୍ନ ହାଇସ୍ପେସ୍, କୀ ଅର୍ଡର୍, ଏନକୋଡିଂ) ଏବଂ ପ୍ରତ୍ୟେକ ସାଇନ୍ ଅବୈଧ ଦେଖାଯିବ। କଚା ଶରୀର ଗ୍ରହଣ କରିବାକୁ ରାସ୍ତାକୁ କନଫିଗର୍ କରନ୍ତୁ (Express: express.raw, Flask: request.get_data), ପ୍ରଥମେ ଯାଞ୍ଚ କରନ୍ତୁ, ପରେ ପାର୍ସ୍ କରନ୍ତୁ।

Webhook handler ମଧ୍ୟରେ ସତ୍ୟ କାମ କରୁଛି

Webhook ଗୁଡିକର ଆକ୍ରମଣାତ୍ମକ ପୁନରାବୃତ୍ତି ନୀତି ଅଛି। ଯଦି ଆପଣଙ୍କର ହ୍ୟାଣ୍ଡଲର୍ କାମ ପ୍ରଦାନ କରିବା ପାଇଁ 30 ସେକେଣ୍ଡ୍ ନେଉଛି, ତେବେ ପ୍ରେରକର ସମୟ ସୀମା ଚାଲୁ ହୁଏ ଏବଂ webhook ପୁନଃପ୍ରେରଣ କରାଯାଏ - ଏବେ ଆପଣଙ୍କର ସେହି ଦେୟ ପାଇଁ ଦୁଇଟି ଡେଲିଭରୀ ଉଡୁଛି। ସଦା: ପ୍ରମାଣିତ କରନ୍ତୁ, ଏନକ୍ୟୁ, 2xx ଫେରାନ୍ତୁ। ବାସ୍ତବ କାମ ଏକ ପ୍ରଷ୍ଠଭୂମି କାର୍ଯ୍ୟକର୍ତ୍ତାରେ ଚାଲିଥାଏ ଯାହା ଯେତେବେଳେ ଆବଶ୍ୟକ ହୁଏ ତେବେ ତାହାକୁ ନେଇପାରିବ।

ବ୍ୟବସାୟ ତର୍କ ସଂପ୍ରେଷଣ ପାଇଁ HTTP ସ୍ଥିତି ବ୍ୟବହାର କରିବା।

ଯଦି ଆପଣଙ୍କର ହ୍ୟାଣ୍ଡଲରେ 4xx ଫେରାଇଥାଏ ଯେତେବେଳେ ବ୍ୟବହାରକାରୀ ଆପଣଙ୍କର ପ୍ରଣାଳୀରେ ଅବସ୍ଥିତ ନୁହେଁ, ପ୍ରେରକ ଏହାକୁ 'ଅବୈଧ ଅନୁରୋଧ' ଭାବରେ ବ୍ୟବହାର କରେ ଏବଂ ପୁନର୍ବାର ଚେଷ୍ଟା କରିବାକୁ ବନ୍ଦ କରେ। ଯଦି ଏହା ସେହି ସ୍ଥିତି ପାଇଁ 5xx ଫେରାଇଥାଏ, ପ୍ରେରକ ସଦା ସଦା ପୁନର୍ବାର ଚେଷ୍ଟା କରେ ଏବଂ ଆପଣଙ୍କର କ୍ୟୁ ଭରିଯାଏ। ଘଟଣାକୁ ସୁରକ୍ଷିତ ଭାବରେ ଅବସ୍ଥାନ କରିବା ପରେ 200 ଫେରାଇବେ (କିମ୍ବା ଏହାକୁ ଡୁପ୍ ଭାବରେ ଚିହ୍ନଟ କରିବେ); ବ୍ୟବସାୟ ନିଷ୍ପତ୍ତିଗୁଡିକୁ ପ୍ରକାଶ କରିବା ପାଇଁ କ୍ୟୁ ତର୍କ ବ୍ୟବହାର କରନ୍ତୁ, HTTP ସ୍ଥିତି ନୁହେଁ।

ଇଭେଣ୍ଟ ID ପରିବର୍ତ୍ତେ ପେଲୋଡ୍ ହ୍ୟାସ୍‌ରେ ଇଡେମ୍ପୋଟେନ୍ସୀ

ସେହି ଏଜେଣ୍ଟ ବିଷୟରେ ଦୁଇଟି ଭିନ୍ନ ଘଟଣା (payment.received ଏବଂ ପରବର୍ତ୍ତୀ payment.sent) ଭିନ୍ନ ଦେହ ରହିଛି ଏବଂ ବୈଧ ଭାବରେ ଅଲଗା ପ୍ରକ୍ରିୟାର ଆବଶ୍ୟକତା ରହିଛି। ଯଦି ଆପଣଙ୍କର ଡିଡ୍ୟୁପ୍ ଏକ ଦେହ ହ୍ୟାସ୍ ଉପରେ ଅଛି, ତେବେ ଆପଣ ତାହାର ଗୋଟିଏକୁ ଛାଡ଼ିପାରିବେ। X-Blockchain0x-Event-Id (ପ୍ରତି ଡେଲିଭରୀରେ ବିଶିଷ୍ଟ) ଉପରେ ଡିଡ୍ୟୁପ୍ କରନ୍ତୁ, ଏବଂ ଘଟଣା ପ୍ରକାର ଆପଣଙ୍କର ହ୍ୟାଣ୍ଡଲର କଣ କରିବାକୁ ଚାଲିବ।

ଏକ ଅଲଗା ନିଶ୍ଚୟ କରାଯାଇଥିବା ଘଟଣାକୁ ଆଶା କରୁଛି

shipped event ଗୁଡ଼ିକ ହେଉଛି payment.received, payment.sent, wallet.deployed, ଏବଂ webhook.test - ଅଲଗା confirmation event ନାହିଁ। transfer ଏକ block ରେ ଆସିଲେ payment.received fire ହୁଏ, ଯାହା ଅଧିକାଂଶ କାମ ପାଇଁ ଆପଣଙ୍କ signal। ମହଙ୍ଗା କିମ୍ବା irreversibe କିଛି ପାଇଁ, transactions.get poll କରନ୍ତୁ ଏବଂ କାର୍ଯ୍ୟ କରିବା ପୂର୍ବରୁ ନିଜ confirmation threshold ଲାଗୁ କରନ୍ତୁ; ଅସ୍ତିତ୍ୱହୀନ event ର ପ୍ରତୀକ୍ଷା କରନ୍ତୁ ନାହିଁ।

ପରବର୍ତ୍ତୀ ପଦକ୍ଷେପଗୁଡିକ

ଯେତେବେଳେ ୱେଭୁକ୍ସ ବୁଲେଟ୍ ପ୍ରୁଫ୍ ହେବ।

Webhooks କଷ୍ଟକର ଅଂଶ। ଉପରେ ଥିବା ଚାରିଟି ଆକାର ସହିତ, ବାକୀ କାମ ପ୍ରାୟତଃ କାର୍ଯ୍ୟକଳାପ: ଏକ ପରୀକ୍ଷା ପରିବେଶ ଯାହା ବିଫଳ ପଥଗୁଡିକୁ ବ୍ୟବହାର କରେ, ବ୍ୟୟ ନିୟନ୍ତ୍ରଣ ଯାହା ଉପସ୍ଥାପନ ଏଜେଣ୍ଟ ଆପଣଙ୍କର ହ୍ୟାଣ୍ଡଲରୁ ଅତିରିକ୍ତ କରେ, ଏବଂ ଏକ ଶେଷ ସୁରକ୍ଷା ସମୀକ୍ଷା।

docs.blockchain0x.com ରେ ପୂର୍ଣ୍ଣ ସନ୍ଦର୍ଭ। ହ୍ୱେବ୍‌ହୁକ୍ ଶବ୍ଦକୋଷ: payment mandate. ଉତ୍ପାଦ ସତହ: Payment API.

ଶେଷ ସମୀକ୍ଷା: 2026-05-15। CC BY 4.0 ଅନୁସାରେ ପ୍ରକାଶିତ।

ଲୋଡ୍ ତଳେ ଆପଣ ବିଶ୍ୱସ୍ତ କରିପାରିବେ ଯେ Webhooks।

ସହି, ପୁନର୍ବାର ଚେଷ୍ଟା, ଇଡେମ୍ପୋଟେନ୍ଟ। ଆରମ୍ଭ କରିବାକୁ ମାଗଣା।