Webhooks-এর সংক্ষিপ্ত বিবরণ

ThunderPhone কল চলাকালে বিভিন্ন ঘটনা ঘটলে আপনার সার্ভারে HTTP POST অনুরোধ পাঠায় — একটি ইনবাউন্ড কল শুরু হয়, একটি কল শেষ হয়, একটি গ্রেডিং রান সম্পন্ন হয়, একটি সতর্কতা ট্রিগার হয়, ইত্যাদি। দুটি ডেলিভারি মডেল রয়েছে:

ইভেন্ট ক্যাটালগে থাকা সব দশটি ইভেন্ট টাইপ webhook এন্ডপয়েন্টের মাধ্যমে ডেলিভার করা হয়। ছয়টি কল-লাইফসাইকেল ইভেন্ট (telephony.incoming, telephony.complete, telephony.tool, web.incoming, web.complete, web.tool) লিগ্যাসি একক-URL webhook-এও পাঠানো হয় — আপনার যদি একটি লিগ্যাসি URL এবং একটি মিলযুক্ত এন্ডপয়েন্ট উভয়ই থাকে, তাহলে আপনি উভয় পাথেই ইভেন্টটি পাবেন। ব্লকিং আচরণ (telephony.incoming / web.incoming কনফিগারেশন বিনিময় এবং webhook-মোডের টুল ডিসপ্যাচ) একচেটিয়াভাবে লিগ্যাসি পাথে থাকে; প্রতিটি এন্ডপয়েন্ট ডেলিভারি হলো fire-and-forget বিজ্ঞপ্তি।

পেলোড ফরম্যাট

এন্ডপয়েন্ট ডেলিভারি হলো data, event_id, এবং type-সহ একটি JSON অবজেক্ট:

{
  "data": {
    "call_id": 987654321,
    "from_number": "+14155550199",
    "to_number": "+15551234567"
  },
  "event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
  "type": "telephony.incoming"
}

প্রেরিত প্রতিটি ইভেন্টের জন্য event_id অনন্য। এটি পুনরায় চেষ্টার ক্ষেত্রেও এবং ইভেন্টটি গ্রহণকারী প্রতিটি এন্ডপয়েন্টে একই থাকে — এটি ব্যবহার করে ডিডুপ করুন।

লিগ্যাসি একক-URL webhook একই type এবং data পাঠায়, তবে event_id ছাড়া:

{
  "type": "telephony.incoming",
  "data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}

নেটওয়ার্কে, প্রতিটি বডি ক্যানোনিক্যালভাবে সিরিয়ালাইজ করা হয় — কীগুলো বর্ণানুক্রমে সাজানো, কোনো হোয়াইটস্পেস নেই, UTF-8। এই ডকুমেন্টেশনের সুন্দরভাবে ফরম্যাট করা উদাহরণগুলো শুধুমাত্র পাঠযোগ্যতার জন্য।

ইভেন্ট টাইপ এবং পেলোড ফিল্ডের সম্পূর্ণ তালিকার জন্য ইভেন্ট ক্যাটালগ দেখুন।

স্বাক্ষর যাচাইকরণ

প্রতিটি অনুরোধে X-ThunderPhone-Signature হেডারে কাঁচা অনুরোধ বডি-র ওপর একটি HMAC-SHA256 স্বাক্ষর থাকে। স্বাক্ষর করার কী হলো এন্ডপয়েন্টের secret (অথবা পুরোনো ডেলিভারির জন্য আপনার সংস্থা-স্তরের webhook secret)।

ধাপ

  1. যেকোনো পার্সিংয়ের আগে কাঁচা অনুরোধ বডি পড়ুন।
  2. hmac_sha256(secret, body).hexdigest() গণনা করুন।
  3. X-ThunderPhone-Signature হেডারের সঙ্গে ধ্রুব সময়ে তুলনা করুন।

আমরা ঠিক যে বাইটগুলো পাঠাই, সেগুলোতেই স্বাক্ষর করি এবং সেই বাইটগুলো হলো ক্যানোনিক্যাল JSON সিরিয়ালাইজেশন (সাজানো কী, কমপ্যাক্ট সেপারেটর)। তাই কাঁচা বডির বিপরীতে যাচাই সবসময় কাজ করে — এবং আপনার ফ্রেমওয়ার্ক যদি শুধু পার্স করা JSON দেয়, তাহলে সাজানো কী ও কমপ্যাক্ট সেপারেটর দিয়ে সেটি আবার সিরিয়ালাইজ করলে অভিন্ন বাইট তৈরি হয়। উভয় পদ্ধতিই যাচাইকরণ নির্দেশিকায় অন্তর্ভুক্ত আছে।

import hmac
import hashlib

def verify_signature(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode("utf-8"),
        body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature or "")

# Example Flask handler
from flask import Flask, request, abort
app = Flask(__name__)

@app.post("/thunderphone-webhook")
def handle():
    body = request.get_data()
    sig = request.headers.get("X-ThunderPhone-Signature", "")
    if not verify_signature(body, sig, WEBHOOK_SECRET):
        abort(401)
    event = request.get_json()
    # dispatch on event["type"] …
    return "", 204
import crypto from "node:crypto";
import express from "express";

function verifySignature(body, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  if (!signature || expected.length !== signature.length) return false;
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature),
  );
}

const app = express();
app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const sig = req.header("X-ThunderPhone-Signature") || "";
    if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));
    // dispatch on event.type …
    res.sendStatus(204);
  },
);

ডেলিভারি সেম্যান্টিক্স

এই সেম্যান্টিক্সগুলো এন্ডপয়েন্ট ডেলিভারির ক্ষেত্রে প্রযোজ্য। লিগ্যাসি একক-URL ওয়েবহুক হলো কোনো পুনঃচেষ্টা ছাড়াই একবারের সিঙ্ক্রোনাস প্রচেষ্টা।

পুনঃচেষ্টা

প্রতিটি ইভেন্টে তাৎক্ষণিকভাবে একবার চেষ্টা করা হয়। যেকোনো 2xx প্রতিক্রিয়া ডেলিভারি স্বীকার করে। অন্য যেকোনো ফলাফলে (non-2xx, সংযোগ ত্রুটি, টাইমআউট) আমরা প্রথম প্রচেষ্টার 1 মিনিট, 5 মিনিট, 30 মিনিট, 2 ঘণ্টা, 6 ঘণ্টা, 12 ঘণ্টা এবং 24 ঘণ্টা পরে পুনঃচেষ্টা করি — 24 ঘণ্টাজুড়ে 8টি প্রচেষ্টা। প্রতিটি প্রচেষ্টা ব্যর্থ হলে ডেলিভারি বন্ধ হয় এবং এন্ডপয়েন্টকে ওয়েবহুক এন্ডপয়েন্টে status="failing" হিসেবে চিহ্নিত করা হয়। পেলোড স্থায়ীভাবে গ্রহণ করা মাত্রই 2xx ফেরত দিন; অ্যাসিঙ্ক্রোনাসভাবে প্রক্রিয়া করুন।

ক্রম

ডেলিভারি ক্রম সর্বোচ্চ প্রচেষ্টাভিত্তিক। বাস্তবে ইভেন্টগুলো যে ক্রমে নির্গত হয় আমরা সাধারণত সেই ক্রমেই ডেলিভার করি, তবে ব্যর্থতার ক্ষেত্রে পুনঃচেষ্টা ক্রম বদলে দিতে পারে। সবসময় call_id / অবজেক্ট আইডি অনুযায়ী ডিডুপ্লিকেট এবং সমন্বয় করুন।

ডুপ্লিকেট

ডেলিভারি অন্তত-একবার: আমরা যে প্রতিক্রিয়া কখনো পাইনি, তার পরের পুনঃচেষ্টায় একটি ইভেন্ট ডুপ্লিকেট হতে পারে। প্রতিটি পুনঃচেষ্টায় একই event_id থাকে, তাই প্রক্রিয়াকৃত আইডি সংরক্ষণ করুন এবং পুনরাবৃত্তি এড়িয়ে চলুন। event_id এন্ডপয়েন্টগুলোর মধ্যেও ভাগ করা হয় — একই ইভেন্টে সাবস্ক্রাইব করা দুটি এন্ডপয়েন্ট একই event_id পায়।

টাইমআউট

এন্ডপয়েন্ট ডেলিভারিতে প্রতিটি প্রচেষ্টার জন্য 30 সেকেন্ড টাইমআউট থাকে। লিগ্যাসি পথে, লাইভ কলের আচরণ নিয়ন্ত্রণকারী ব্লকিং অনুরোধগুলো — telephony.incoming / web.incoming কনফিগারেশন বিনিময় — 10 সেকেন্ড পরে টাইমআউট হয়, তবে ধীর প্রতিক্রিয়া কল ধরা বিলম্বিত করে, তাই কয়েক সেকেন্ডের মধ্যে উত্তর দেওয়ার লক্ষ্য রাখুন। ওয়েবহুক-মোড টুল ডিসপ্যাচ 20 সেকেন্ডের অনুমতি দেয়।

উৎস IP

আউটবাউন্ড ওয়েবহুক ThunderPhone-এর ক্লাউড IP রেঞ্জ থেকে আসে। আপনার ফায়ারওয়ালে অ্যালাউলিস্ট প্রয়োজন হলে সহায়তা দলের সঙ্গে যোগাযোগ করুন, আমরা বর্তমান রেঞ্জগুলো ভাগ করে দেব।

লিগ্যাসি এবং এন্ডপয়েন্ট-ভিত্তিক ওয়েবহুকের মধ্যে নির্বাচন

বৈশিষ্ট্যলিগ্যাসি (/v1/webhook)এন্ডপয়েন্ট (/v1/developer/webhook-endpoints)
URL-এর সংখ্যাপ্রতি সংস্থায় 1টিপ্রতি সংস্থায় একাধিক
ইভেন্ট কভারেজশুধুমাত্র telephony.* / web.*সব 10টি ইভেন্ট ধরন
ইভেন্ট ফিল্টারপ্রতি এন্ডপয়েন্টে
পুনঃচেষ্টানেই24 ঘণ্টায় 8টি প্রচেষ্টা
এনভেলপtype + datatype + data + event_id
সিক্রেট রোটেশনএকক সিক্রেট প্রতিস্থাপন করেপ্রতি এন্ডপয়েন্টে সিক্রেট
মুছে না ফেলে নিষ্ক্রিয় করাstatus=disabled
স্ট্যাটাস দৃশ্যমানতাactive / disabled / failing
ব্লকিং কনফিগারেশন বিনিময়হ্যাঁ (telephony.incoming / web.incoming)কখনো নয় — শুধুমাত্র নোটিফিকেশন
এর জন্য সেরাডাইনামিক কল কনফিগারেশনপ্রোডাকশনে ইভেন্ট গ্রহণ

নতুন ইন্টিগ্রেশনগুলোর এন্ডপয়েন্ট-ভিত্তিক ওয়েবহুকের মাধ্যমে ইভেন্ট গ্রহণ করা উচিত। শুধুমাত্র কল ধরার সময় ডাইনামিকভাবে কনফিগার করলে অথবা ওয়েবহুক-মোড টুল ডিসপ্যাচ ব্যবহার করলে একটি লিগ্যাসি URL রাখুন (অথবা যোগ করুন) — এই অনুরোধ/প্রতিক্রিয়া বিনিময়গুলো কেবল লিগ্যাসি পথেই চলে।


সম্পর্কিত