ওয়েবহুক ব্যাখ্যা: সাইনড পেলোড ও নির্ভরযোগ্য রিট্রাই
আপনি একটি পেমেন্ট ইন্টিগ্রেশন শিপ করলেন, প্রোভাইডার একটি ওয়েবহুক ফায়ার করল, আপনার এন্ডপয়েন্ট 200 ফেরত দিল — কিন্তু তিন ঘণ্টা পরে সাপোর্ট রিপোর্ট করল অর্ডার মিসিং। বা আরও খারাপ: একই ইভেন্ট চারবার এল এবং কাস্টমারকে দুইবার চার্জ করা হলো। ওয়েবহুক দেখতে সহজ (আপনার URL-এ একটি HTTP POST) কিন্তু কঠিন অংশগুলো হলো বিশ্বাস এবং ডেলিভারি গ্যারান্টি।
এই গাইডে দুটোই কভার করা হয়েছে: কীভাবে সাইনড পেলোড ব্যবহার করে যাচাই করবেন যে পেলোডটি সত্যিই প্রেরকের কাছ থেকে এসেছে, এবং কীভাবে এমন একটি রিট্রাই মডেল তৈরি করবেন যা ডাউনটাইম, টাইমআউট এবং ডুপ্লিকেট ডেলিভারি সত্ত্বেও কাজ করে।
ওয়েবহুক আসলে কী
ওয়েবহুক হলো একটি রিভার্স API কল। আপনার সার্ভার প্রতি মিনিটে GET /events পোল করার বদলে, প্রোভাইডার কিছু ঘটলে আপনার রেজিস্টার করা URL-এ একটি HTTP রিকোয়েস্ট পুশ করে। পেলোড সাধারণত JSON, মেথড প্রায় সবসময় POST, এবং আপনি যে রেসপন্স দেন তা একটি সংকেত: 2xx মানে "গৃহীত", অন্য কিছু মানে "পরে আবার চেষ্টা করুন"।
শেষ বাক্যটিই পুরো চুক্তি। বাকি সব — সিগনেচার, রিট্রাই, অর্ডারিং — এই চুক্তিকে একটি অবিশ্বস্ত নেটওয়ার্কে নিরাপদ করতে বিদ্যমান।
আনসাইনড ওয়েবহুক কেন বিপজ্জনক
আপনার ওয়েবহুক এন্ডপয়েন্ট একটি পাবলিক URL। যে কেউ এটি খুঁজে পেলে একটি অর্ডার পেইড হয়েছে বলে দাবি করে জাল পেলোড POST করতে পারে। যদি আপনি যাচাই ছাড়াই বডিতে বিশ্বাস করেন, তাহলে আপনার বিলিং লজিকে একটি খোলা দরজা তৈরি হয়েছে।
IP অ্যালাউলিস্ট কিছুটা সাহায্য করে কিন্তু CDN, সার্ভারলেস এগ্রেস এবং প্রোভাইডারের IP পরিবর্তনের কারণে ভেঙে পড়ে। শক্তিশালী সমাধান হলো শেয়ার্ড সিক্রেট প্লাস কাঁচা রিকোয়েস্ট বডির উপর একটি HMAC সিগনেচার।
সাইনড পেলোড কীভাবে কাজ করে
প্রোভাইডার এবং আপনি একটি সিক্রেট কী শেয়ার করেন। প্রতিটি ডেলিভারিতে প্রোভাইডার কাঁচা বডির উপর (কখনও টাইমস্ট্যাম্প প্লাস বডির উপর) একটি HMAC (সাধারণত SHA-256) গণনা করে এবং হেক্স ডাইজেস্টটি X-Signature-এর মতো একটি হেডারে পাঠায়। আপনি সিক্রেটের আপনার কপি দিয়ে একই ডাইজেস্ট পুনরায় গণনা করেন এবং তুলনা করেন।
দুটি বিষয় মানুষ যতটা আশা করে তার চেয়ে বেশি গুরুত্বপূর্ণ:
- কাঁচা বাইটে সাইন করুন। যদি আপনার ফ্রেমওয়ার্ক JSON পার্স করে পুনরায় সিরিয়ালাইজ করে, কী অর্ডার এবং হোয়াইটস্পেস পরিবর্তিত হয় এবং ডাইজেস্ট মিলবে না। প্রথমে কাঁচা বডি পড়ুন।
- কনস্ট্যান্ট টাইমে তুলনা করুন। একটি সাধারণ
==টাইমিং তথ্য ফাঁস করে। আপনার ভাষার ক্রিপ্টো লাইব্রেরি থেকে একটি কনস্ট্যান্ট-টাইম তুলনা ফাংশন ব্যবহার করুন।
একটি ন্যূনতম যাচাই উদাহরণ (Node.js)
const crypto = require('crypto');
function verifySignature(rawBody, headerSig, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody) // raw Buffer or string, not parsed JSON
.digest('hex');
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(headerSig || '', 'utf8');
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}
একই প্যাটার্ন Python-এ hmac.compare_digest, Go-তে hmac.Equal, এবং Java-তে MessageDigest.isEqual দিয়ে কাজ করে। নীতিটি সর্বত্র অভিন্ন: কাঁচা বাইটের উপর HMAC, কনস্ট্যান্ট-টাইম তুলনা।
টাইমস্ট্যাম্প দিয়ে রিপ্লে সুরক্ষা
একটি বৈধ সিগনেচার প্রমাণ করে যে পেলোডটি সিক্রেট ধারণকারী কারও কাছ থেকে এসেছে — কিন্তু একটি ক্যাপচার করা রিকোয়েস্ট চিরকালের জন্য রিপ্লে করা যেতে পারে। যে প্রোভাইডাররা এটি গুরুত্ব দেয় তারা সাইনড স্ট্রিংয়ে একটি টাইমস্ট্যাম্প অন্তর্ভুক্ত করে, উদাহরণস্বরূপ t=1712345678,v1=<hex>। আপনার দিকে:
- হেডার থেকে টাইমস্ট্যাম্প পার্স করুন।
- যদি এটি একটি সহনশীলতা উইন্ডো (প্রায়শই ৫ মিনিট) থেকে পুরোনো হয় তবে রিকোয়েস্টটি প্রত্যাখ্যান করুন।
timestamp + "." + rawBody-এর উপর HMAC পুনরায় গণনা করুন।
এটি একটি ফাঁস হওয়া রিকোয়েস্টের ক্ষতিকে চিরকালের পরিবর্তে কয়েক মিনিটে সীমাবদ্ধ করে।
এমন রিট্রাই ডিজাইন করা যা সত্যিই ডেলিভার করে
নেটওয়ার্ক ব্যর্থ হয়, আপনার সার্ভিস রিস্টার্ট হয়, ডিপ্লয় পড রোল করে। একটি গুরুতর প্রোভাইডার ব্যর্থ ডেলিভারিগুলো একটি সময়সূচিতে রিট্রাই করে। একটি গুরুতর রিসিভারকে অবশ্যই ধরে নিতে হবে যে প্রতিটি ইভেন্ট একাধিকবার আসতে পারে।
প্রেরক-পক্ষের রিট্রাই নিয়ম
- যেকোনো নন-2xx রেসপন্স, কানেকশন ত্রুটি বা টাইমআউটকে ব্যর্থতা হিসেবে গণ্য করুন।
- এক্সপোনেনশিয়াল ব্যাকঅফ এবং জিটার দিয়ে রিট্রাই করুন — উদাহরণস্বরূপ 1s, 5s, 30s, 5m, 30m, 2h — যাতে প্রোভাইডার আউটেজ রিকভারির সময় থান্ডারিং হার্ড তৈরি না করে।
- প্রচেষ্টার সংখ্যা সীমাবদ্ধ করুন (সাধারণত ৫–১০) এবং চূড়ান্ত ব্যর্থতা রেকর্ড করুন যাতে ব্যবহারকারী বা অপারেটর এটি দেখতে পারেন।
- একটি সংক্ষিপ্ত ক্লায়েন্ট টাইমআউট সেট করুন (৫–১০ সেকেন্ড)। একটি ওয়েবহুক হ্যান্ডলার দ্রুত স্বীকার করে অ্যাসিনক্রোনাসভাবে কাজ করা উচিত।
রিসিভার-পক্ষের নিয়ম
- প্রথমে যাচাই করুন, পরে পার্স করুন। আপনার ডেটাবেস স্পর্শ করার আগে 401 দিয়ে খারাপ সিগনেচার প্রত্যাখ্যান করুন।
- ইভেন্ট সংরক্ষণ করুন, তারপর 200 ফেরত দিন। কাঁচা পেলোড এবং ইভেন্ট আইডি একটি কিউ বা টেবিলে লিখুন, অবিলম্বে সাড়া দিন এবং একটি ওয়ার্কারে প্রসেস করুন। যদি প্রসেসিং ব্যর্থ হয়, অভ্যন্তরীণভাবে রিট্রাই করুন।
- হ্যান্ডলার আইডেম্পোটেন্ট করুন। প্রোভাইডারের ইভেন্ট আইডি একটি ইউনিক কী হিসেবে ব্যবহার করুন। যদি এটি ইতিমধ্যে বিদ্যমান থাকে, সাইড ইফেক্ট পুনরায় না চালিয়ে 200 ফেরত দিন।
- সততার সাথে ত্রুটি ফেরত দিন। যদি আপনার ডেটাবেস ডাউন থাকে, 500 ফেরত দিন যাতে প্রোভাইডার রিট্রাই করে — 200 ফেরত দিয়ে চুপচাপ ইভেন্ট ড্রপ করবেন না।
| রেসপন্স | প্রেরকের কাছে অর্থ | পরবর্তী কী ঘটে |
|---|---|---|
| 200 / 204 | ডেলিভার হয়েছে | রিট্রাই নেই |
| 400 | ম্যালফর্মড বা প্রত্যাখ্যাত | সাধারণত রিট্রাই নেই; ডিবাগিংয়ের জন্য লগ করুন |
| 401 | সিগনেচার অবৈধ | রিট্রাই নেই; সিক্রেট তদন্ত করুন |
| 429 | রেট লিমিটেড | ব্যাকঅফ সহ রিট্রাই |
| 5xx / টাইমআউট | সাময়িক ব্যর্থতা | ব্যাকঅফ সহ রিট্রাই |
বাস্তবে আইডেম্পোটেন্সি
আইডেম্পোটেন্সি এখানে কোনো বাজওয়ার্ড নয় — এটি একটি রিট্রাই এবং দ্বিগুণ চার্জের মধ্যে দাঁড়ানো একমাত্র জিনিস। প্যাটার্ন:
INSERT INTO webhook_events (event_id, payload, received_at)
VALUES ($1, $2, now())
ON CONFLICT (event_id) DO NOTHING;
যদি ইনসার্ট শূন্য সারিতে প্রভাব ফেলে, আপনি ইতিমধ্যে এই ইভেন্টটি দেখেছেন; স্বীকার করুন এবং এগিয়ে যান। এটি একসাথে আসা ডেলিভারির ক্ষেত্রেও কাজ করে কারণ ইউনিক কনস্ট্রেইন্ট ডেটাবেসে সেগুলোকে সিরিয়ালাইজ করে।
যে সাইড ইফেক্টগুলো ঠিক একবার ঘটতে হবে (একটি ইমেইল পাঠানো, একটি কার্ড চার্জ করা), সেগুলোর জন্য আপনার নিজের আইডেম্পোটেন্সি কী ডাউনস্ট্রিমে পাস করুন — বেশিরভাগ পেমেন্ট এবং ইমেইল API একটি গ্রহণ করে।
অর্ডারিং, ডিবাগিং এবং অবজারভেবিলিটি
ধরে নেবেন না যে ইভেন্টগুলো ক্রমানুসারে আসে। রিট্রাই ডেলিভারি পুনর্বিন্যাস করতে পারে, এবং সমান্তরাল ওয়ার্কাররাও তাই করবে। যদি ক্রম গুরুত্বপূর্ণ হয়, পেলোডে একটি সিকোয়েন্স নম্বর বা টাইমস্ট্যাম্প অন্তর্ভুক্ত করুন এবং অন্ধভাবে ডেল্টা প্রয়োগ না করে স্টেট পুনর্মিলন করুন।
প্রতিটি ডেলিভারি প্রচেষ্টা ইভেন্ট আইডি, সিগনেচার ফলাফল, রেসপন্স কোড এবং প্রসেসিং সময় সহ লগ করুন। যখন একজন কাস্টমার বলেন "আমার অর্ডার কখনও আসেনি", আপনি সেকেন্ডের মধ্যে উত্তর দিতে চান: ওয়েবহুক কি গৃহীত হয়েছিল, সিগনেচার কি বৈধ ছিল, প্রসেসিং কি সফল হয়েছিল? প্রতি ইভেন্টে একটি স্ট্রাকচার্ড লগ লাইনই যথেষ্ট।
যে এন্ডপয়েন্টগুলো অনেক ট্রাফিক পায়, সেগুলোর জন্য ডিপ্লয়ের পরে রিট্রাই স্টর্মের দিকে নজর রাখুন। সেগুলো চিহ্নিত করার একটি দ্রুত উপায় হলো আপনার অ্যাক্সেস লগে রিকোয়েস্ট প্যাটার্নগুলো দেখা — একটি IP থেকে 5xx রেসপন্স সহ অভিন্ন পাথের স্পাইক একটি ক্লাসিক রিট্রাই লুপ। Nginx লগ অ্যানালাইজার-এর মতো টুল সেই প্যাটার্নটি হাতে একটি কোয়েরি না লিখেই স্পষ্ট করে তোলে।
এড়িয়ে চলার সাধারণ ভুল
- সিগনেচার যাচাই করার আগে JSON পার্স করা।
- পরিবেশ জুড়ে একই সিক্রেট ব্যবহার করা, বা কখনও রোটেট না করা।
- রিকোয়েস্ট হ্যান্ডলারের ভিতরে ভারী কাজ (PDF জেনারেশন, থার্ড-পার্টি কল) করা।
- প্রসেসিং ত্রুটিতে 200 ফেরত দেওয়া, যাতে প্রোভাইডার কখনও রিট্রাই না করে।
- যখন গুরুত্বপূর্ণ, প্রোভাইডারের API থেকে রিসোর্স পুনরায় না এনে পেলোডের পরিমাণ বা স্ট্যাটাসে বিশ্বাস করা।
সাধারণ প্রশ্ন
যদি বডি JSON হয় তবে আমি কীভাবে ওয়েবহুক সিগনেচার যাচাই করব?
যেকোনো JSON পার্সিংয়ের আগে কাঁচা রিকোয়েস্ট বডি বাইট হিসেবে পড়ুন, সেই বাইটের উপর HMAC গণনা করুন এবং একটি কনস্ট্যান্ট-টাইম ফাংশন ব্যবহার করে হেডারের সাথে তুলনা করুন। পুনরায় সিরিয়ালাইজ করা JSON ভিন্ন ডাইজেস্ট তৈরি করবে।
একটি ওয়েবহুক কতবার রিট্রাই করা উচিত?
কোনো সার্বজনীন সংখ্যা নেই, তবে এক্সপোনেনশিয়াল ব্যাকঅফ এবং জিটার সহ কয়েক ঘণ্টা ধরে ৫–১০টি প্রচেষ্টা সাধারণ। গুরুত্বপূর্ণ部分是 রিসিভাররা আইডেম্পোটেন্ট, তাই রিট্রাই ঘন ঘন হলেও নিরাপদ।
আমার ওয়েবহুক এন্ডপয়েন্ট কী স্ট্যাটাস কোড ফেরত দেবে?
ইভেন্টটি টেকসইভাবে সংরক্ষণ করার পরেই 2xx ফেরত দিন। অবৈধ সিগনেচারের জন্য 401, ম্যালফর্মড পেলোডের জন্য 400, এবং যখন আপনি চান প্রেরক রিট্রাই করুক তখন 5xx বা 429 ফেরত দিন।
যদি আপনি ওয়েবহুক পেলোড পরীক্ষা করছেন বা আপনি যে JSON পান তা লগ করছেন, তাহলে JSON ফরম্যাটার হলো পার্সিং লজিক তৈরির আগে একটি ক্যাপচার করা ইভেন্টকে সুন্দরভাবে প্রিন্ট এবং যাচাই করার একটি দ্রুত উপায়।