Skip to content

Webhook

Byl нь шинэ event бүрийг webhook хүсэлтээр таны бакэнд системд цаг алдалгүй хүргэнэ.

Хүсэлт бүр криптограф гарын үсэгтэй байх тул үүнийг шалгах замаар яг манайхаас ирсэн эсэхийг хялбар шалган хамгаалалт хийх боломжтой юм.

Webhook-г POST хүсэлт илгээн дуудах бөгөөд payload нь тухайн event-ийн төрөл болон хамаарах объектын мэдээллийг багтаасан байна.

Payload-ийн бүтэц

Бүх event ижил бүтэцтэй JSON байна:

ТалбарТайлбар
idEvent-ийн дахин давтагдашгүй дугаар.
project_idEvent хамаарах төслийн ID.
typeEvent-ийн төрөл (жш: invoice.paid).
objectdata.object-т ямар объект байгааг заана (жш: invoice).
data.objectEvent хамаарах объект бүтнээрээ (нэхэмжлэх, checkout гэх мэт).
created_atEvent үүссэн огноо.

Давхардсан event-ээс сэргийлэх

Ховор тохиолдолд нэг event танд нэгээс олон удаа очиж болзошгүй (жишээ нь таны сервер хариу өгсөн ч сүлжээний алдаагаар бидэнд хүрээгүй үед). Боловсруулсан event-ийн id-г хадгалж, өмнө нь ирсэн id-тэй event-ийг алгасахыг зөвлөж байна (idempotency).

Төсөлд webhook тохируулах

Удирдлагын буланд төслийнхөө Webhook цэсээр орж шинээр бүртгэнэ. Нэг төсөлд өөрийн идэвхжүүлсэн багцын хязгаар хүртэлх хэдэн ч тооны webhook тохируулан ашиглах боломжтой.

Ямар ч хаяг тохируулан ашиглах боломжтой ч https-ээр хамгаалагдсан url хаягийг тохируулан ашиглахыг бид танд зөвлөж байна.

Хамгаалалт ба гарын үсэг

Бүх webhook хүсэлтүүд Byl-Signature header-тэй очих бөгөөд энэ нь тухайн event-ийн дахин давтагдашгүй гарын үсэг юм.

Бид гарын үсгийг хүсэлтийн түүхий (raw) body-гоор дараах байдлаар үүсгэдэг:

php
$payload = $request->getContent(); // түүхий body. Задлаад дахин encode хийхгүй.
$computedSignature = hash_hmac('sha256', $payload, $secret);

Иймд таны бакэнд систем ирсэн хүсэлтийн түүхий body-гоор гарын үсгийг дахин үүсгээд Byl-Signature header-ээр ирсэн гарын үсэгтэй харьцуулах хэрэгтэй. JSON-ыг задлаад дахин json_encode хийвэл түлхүүрийн дараалал өөрчлөгдөж гарын үсэг таарахгүй болох магадлалтай.

Харьцуулахдаа === биш, тогтмол хугацааны (constant-time) харьцуулалт ашиглана уу — PHP-д hash_equals(), Node.js-д crypto.timingSafeEqual():

php
if (! hash_equals($computedSignature, $request->header('Byl-Signature'))) {
    abort(401);
}

Та гарын үсгийн secret мэдээллийг удирдлагын булан дахь webhook-ийн дэлгэрэнгүй хуудаснаас харах боломжтой. secret нь багийн бүх төсөлд нэг ижил байна.

Гуравдагч этгээдийг зүй бус үйлдлээс сэргийлэхийн тулд хүсэлт бүрийг заавал шалгаж байхыг бид танд зөвлөж байна.

Жишээ nodejs гарын үсэг үүсгэх код:

js
const crypto = require("crypto");

let secret = "your_secret_here";
let payload = rawRequestBody; // the raw body string, not JSON.stringify(req.body)
let computedSignature = crypto
  .createHmac("sha256", secret)
  .update(payload)
  .digest("hex");

Жишээ апп

Хэрэв холболт хийсэн жишээ код хэрэгтэй бол дараах repository-г харна уу:

Webhook хүсэлтийн алдаа

Хэрэв webhook хүсэлт илгээх үед таны серверээс HTTP/200 хариу ирсэн бол бид амжилттайд тооцдог. Өөр хариу ирсэн эсвэл хүсэлт хугацаа хэтэрсэн бол алдаатай гэж үзэн 1 цагийн дараа дахин илгээдэг.

Нийт 3 удаа амжилтгүй болсон үед дахин илгээхээ зогсоох болно. Амжилтгүй хүсэлтийн талаарх мэдээллийг удирдлагын булангаас харах боломжтой. Хэрэв та алдаагаа зассан бол дахин илгээх товчлуур дараарай.

Удирдлагын буланд сүүлд илгээсэн хүсэлтүүд, тэдгээрийн payload болон таны серверээс ямар хариу ирснийг харах боломжтой.

Webhook event-үүд

Дараах төрлийн event-үүдийг бид одоогоор илгээж байна.

EventТайлбар
invoice.paidНэхэмжлэх амжилттай төлөгдсөн.
invoice.voidНэхэмжлэх хүчингүй болгогдсон.
checkout.completedCheckout амжилттай төлөгдсөн.
subscription.createdШинэ багц үүссэн.
subscription.renewedБагцын мөчлөг сунгагдсан.
subscription.updatedБагц солигдсон, цуцлалт хүссэн эсвэл буцсан.
subscription.renewal_dueСунгалтын сануулга илгээгдсэн.
subscription.past_dueБагцын хугацаа хэтэрсэн (grace period).
subscription.canceledБагц бүрэн цуцлагдсан.

invoice.paid

Нэхэмжлэх амжилттай төлөгдсөн үед invoice.paid төрөлтэй event илгээх болно. data.object-т төлөгдсөн нэхэмжлэхийн объект очих болно.

json
{
  "id": 3,
  "project_id": 1,
  "type": "invoice.paid",
  "object": "invoice",
  "data": {
    "object": {
      "id": 71,
      "amount": 10,
      "number": "TEST-0003",
      "status": "paid",
      "due_date": "2025-08-08T16:14:07.000000Z",
      "created_at": "2025-08-07T16:14:07.000000Z",
      "project_id": 1,
      "updated_at": "2025-08-07T16:14:16.000000Z",
      "description": "First invoice",
      "url": "https://byl.mn/h/invoice/5708/XN3GbRBxTslkMCeDj10CJtqlHiPfcmZ8"
    }
  },
  "created_at": "2025-08-07T16:14:16.000000Z",
  "updated_at": "2025-08-07T16:14:16.000000Z"
}

checkout.completed

Checkout амжилттай төлөгдсөн үед checkout.completed төрөлтэй event илгээх болно. data.object-т төлөгдсөн checkout объект очих болно.

json
{
  "id": 59,
  "project_id": 5,
  "type": "checkout.completed",
  "object": "checkout",
  "data": {
    "object": {
      "id": 13338,
      "url": "https://byl.mn/h/checkout/13338/Yi7smBuk",
      "items": [
        {
          "id": 69,
          "price": {
            "id": 17,
            "product": {
              "id": 15,
              "name": "Product 1",
              "created_at": "2025-08-03T10:15:50.000000Z",
              "project_id": 5,
              "updated_at": "2025-08-03T10:15:50.000000Z",
              "client_reference_id": null
            },
            "created_at": "2025-08-03T10:17:07.000000Z",
            "product_id": 15,
            "updated_at": "2025-08-03T10:17:07.000000Z",
            "unit_amount": 50
          },
          "price_id": 17,
          "quantity": 1,
          "created_at": "2025-08-03T10:17:07.000000Z",
          "updated_at": "2025-08-03T10:17:07.000000Z",
          "amount_unit": 50,
          "checkout_id": 64,
          "amount_total": 50,
          "amount_subtotal": 50
        }
      ],
      "status": "complete",
      "is_guest": true,
      "cancel_url": null,
      "created_at": "2025-08-03T10:17:07.000000Z",
      "expires_at": "2025-08-03T16:00:00.000000Z",
      "project_id": 5,
      "receipt_id": null,
      "updated_at": "2025-08-03T10:18:43.000000Z",
      "customer_id": null,
      "success_url": "https://will.iam/success",
      "amount_total": 50,
      "phone_number": "99999999",
      "customer_email": "[email protected]",
      "amount_subtotal": 50,
      "payment_method": "qpay",
      "client_reference_id": null,
      "phone_number_collection": true,
      "email_collection": true,
      "coupon_codes": [
        {
          "code": "SUMMER2025",
          "coupon_name": "Summer Sale",
          "discount_amount": "10.00",
          "redeemed_at": "2025-08-03T10:18:43.000000Z"
        }
      ]
    }
  },
  "created_at": "2025-08-03T10:18:43.000000Z",
  "updated_at": "2025-08-03T10:18:43.000000Z"
}

coupon_codes талбар нь checkout-д хэрэглэгдсэн хөнгөлөлтийн кодуудын мэдээллийг агуулна. Хэрэв checkout-д хөнгөлөлтийн код хэрэглэгдээгүй бол энэ талбар хоосон массив байна.

coupon_codes массивын элемент бүр дараах талбаруудыг агуулна:

  • code: Хөнгөлөлтийн код
  • coupon_name: Хөнгөлөлтийн нэр
  • discount_amount: Хөнгөлөлтийн дүн
  • redeemed_at: Хөнгөлөлт хэрэглэгдсэн огноо

subscription.*

Багцын төлөвт өөрчлөлт орох бүрд subscription. угтвартай event илгээгдэнэ (төрлүүдийг дээрх хүснэгтээс харна уу). data.object-т багцын объект харилцагч, бүтээгдэхүүн, үнийн мэдээллийн хамт очно.

json
{
  "id": 87,
  "project_id": 1,
  "type": "subscription.renewed",
  "object": "subscription",
  "data": {
    "object": {
      "id": 4,
      "project_id": 1,
      "status": "active",
      "customer": {
        "id": 12,
        "client_reference_id": "user_842",
        "name": "Бат-Эрдэнэ",
        "email": "[email protected]"
      },
      "product": {
        "id": 7,
        "name": "Starter багц",
        "client_reference_id": null
      },
      "price": {
        "id": 3,
        "type": "recurring",
        "unit_amount": 30000,
        "recurring_interval": "month",
        "recurring_interval_count": 1
      },
      "current_period_start": "2026-07-23T04:10:00.000000Z",
      "current_period_end": "2026-08-23T15:59:59.000000Z",
      "trial_ends_at": null,
      "canceled_at": null,
      "is_test": false,
      "created_at": "2026-06-23T04:10:00.000000Z",
      "updated_at": "2026-07-23T04:10:00.000000Z"
    }
  },
  "created_at": "2026-07-23T04:10:00.000000Z",
  "updated_at": "2026-07-23T04:10:00.000000Z"
}

Хэрэглэгчийн эрхийг subscription.canceled ирэх үед хаана — цуцлалт хүссэн ч мөчлөг дуустал эрх хүчинтэй байдаг тул subscription.updated (canceled_at бөглөгдсөн) дээр эрх хаахгүй. Дэлгэрэнгүйг Subscriptions хуудаснаас уншина уу.