Webhook
Танай системд нэхэмжлэх төлөгдөх, багц сунгагдах зэрэг үйлдэл болмогц Byl тэр даруйд нь таны сервер рүү POST хүсэлт илгээдэг — үүнийг webhook гэнэ. Ингэснээр та төлбөрийн төлөвийг байнга асууж шалгах (polling) шаардлагагүйгээр event болмогц мэдэж, захиалга баталгаажуулах, эрх нээх зэрэг үйлдлээ автоматжуулах боломжтой.
Ажиллах зарчим нь гуравхан алхам:
- Удирдлагын буланд endpoint (хүлээн авах URL) бүртгэнэ.
- Event болмогц бид тухайн URL руу event-ийн мэдээллийг
POSTхүсэлтээр илгээнэ. - Таны сервер хүсэлтийн гарын үсгийг шалгаад
2xxхариу буцаана.
Endpoint бүртгэх
“Endpoint бүртгэх” хэсэгУдирдлагын буланд төслийнхөө Webhooks цэсээр орж endpoint-ийн URL-ээ бүртгэнэ. Нэг төсөлд идэвхжүүлсэн багцынхаа хязгаарт багтаан хэд хэдэн endpoint бүртгэж болно.
Endpoint-ийн URL дараах шаардлагыг хангасан байх ёстой:
https://-ээр эхэлсэн байх.- Интернэтээс хандах боломжтой байх —
localhostболон дотоод сүлжээний хаягууд ажиллахгүй.
Хөгжүүлэлтийн орчинд туршихдаа ngrok зэрэг tunnel ашиглан локал серверээ түр хугацаанд интернэтэд нээж болно.
Хүсэлтэд хариу өгөх
“Хүсэлтэд хариу өгөх” хэсэгТаны сервер 5 секундын дотор 2xx төлөвтэй хариу буцаавал бид хүргэлтийг амжилттайд тооцно. Өөр төлөв кодтой хариу, redirect, эсвэл хугацаа хэтэрсэн тохиолдолд амжилтгүйд тооцож дахин илгээнэ.
Payload-ийн бүтэц
“Payload-ийн бүтэц” хэсэгБүх event ижил бүтэцтэй JSON байна:
| Талбар | Тайлбар |
|---|---|
id |
Event-ийн дахин давтагдашгүй дугаар. |
project_id |
Event хамаарах төслийн ID. |
business_id |
Төслийн ажилладаг бизнесийн ID. Platform багт харилцагчийг ялгахад; бусдад багийн өөрийн бизнес. |
type |
Event-ийн төрөл (жш: invoice.paid). |
object |
data.object-т ямар объект байгааг заана (жш: invoice). |
data.object |
Event хамаарах объект бүтнээрээ (нэхэмжлэх, checkout гэх мэт). |
created_at |
Event үүссэн огноо. |
Event-ийн төрөл бүрийн жишээг доороос харна уу.
Гарын үсэг шалгах
“Гарын үсэг шалгах” хэсэгWebhook хүлээн авдаг URL тань интернэтэд нээлттэй байдаг тул хэн ч тийшээ хуурамч хүсэлт илгээж болзошгүй. Үүнээс хамгаалахын тулд бид хүсэлт бүрд Byl-Signature header-ээр гарын үсэг дагалдуулдаг — та хүсэлт бүр дээр үүнийг заавал шалгаж байхыг зөвлөж байна.
Гарын үсгийг шалгах гурван алхам:
1. Хүсэлтийн түүхий (raw) body-г ав. JSON-ыг задлаад дахин json_encode хийвэл түлхүүрийн дараалал өөрчлөгдөж гарын үсэг таарахгүй болох магадлалтай тул body-г яг ирснээр нь ашиглана.
2. Secret-ээрээ HMAC-SHA256 гарын үсэг тооцоол. Secret-ээ удирдлагын булан дахь endpoint-ийн дэлгэрэнгүй хуудасны Signing secret хэсгээс харна. Secret нь багийн бүх төсөлд ижил байна.
3. Тооцоолсон гарын үсгээ Byl-Signature header-тэй харьцуул. Харьцуулахдаа === биш, тогтмол хугацааны (constant-time) харьцуулалт ашиглана — PHP-д hash_equals(), Node.js-д crypto.timingSafeEqual().
PHP жишээ:
$payload = $request->getContent(); // түүхий body. Задлаад дахин encode хийхгүй.$computedSignature = hash_hmac('sha256', $payload, $secret);
if (! hash_equals($computedSignature, $request->header('Byl-Signature'))) { abort(401);}Node.js жишээ:
const crypto = require("crypto");
const computedSignature = crypto .createHmac("sha256", secret) .update(rawRequestBody) // түүхий body. JSON.stringify(req.body) биш. .digest("hex");
const isValid = crypto.timingSafeEqual( Buffer.from(computedSignature), Buffer.from(req.headers["byl-signature"] ?? ""),);Бүрэн ажиллах жишээ код хэрэгтэй бол дараах repository-г харна уу:
- Node.js — Express example
Дахин илгээлт
“Дахин илгээлт” хэсэгХүргэлт амжилтгүй болбол бид exponential backoff зарчмаар, өсөх интервалтайгаар автоматаар дахин илгээдэг:
| Оролдлого | Өмнөх оролдлогоос хойш |
|---|---|
| 2 | 5 минут |
| 3 | 30 минут |
| 4 | 2 цаг |
| 5 | 5 цаг |
| 6 | 10 цаг |
| 7 | 24 цаг |
| 8 | 48 цаг |
Өөрөөр хэлбэл эхний оролдлогоос хойш нийт 4 орчим хоногийн турш, 8 хүртэл удаа оролддог. Энэ хугацаанд аль нэг оролдлого амжилттай болбол дахин илгээлт зогсоно.
Бүх оролдлого амжилтгүй болбол хүргэлт failed төлөвт шилжиж, багийн гишүүдэд имэйлээр мэдэгдэнэ (endpoint тус бүрт хоногт нэг удаа). Асуудлаа зассаны дараа удирдлагын булангаас:
- Нэг хүргэлтийг нээгээд Дахин илгээх товчоор дахин илгээх, эсвэл
- Endpoint-ийн хуудасны Амжилтгүйг дахин илгээх товчоор амжилтгүй болсон бүх хүргэлтийг нэг дор дахин илгээх боломжтой.
Endpoint-ийн хуудсанд сүүлд илгээсэн хүсэлтүүд, тэдгээрийн payload, таны серверийн буцаасан хариуны төлөв код харагдана. Хүргэлтийн түүх 30 хоног хадгалагдана.
Endpoint автоматаар идэвхгүй болох
“Endpoint автоматаар идэвхгүй болох” хэсэгEndpoint рүү илгээсэн хүргэлтүүд 7 хоногийн турш тасралтгүй амжилтгүй болбол (энэ хугацаанд нэг ч амжилттай хүргэлтгүй бол) бид тухайн endpoint-ийг автоматаар идэвхгүй болгож, багийн гишүүдэд имэйлээр мэдэгдэнэ.
Идэвхгүй болсон endpoint-ийг удирдлагын булан дахь хуудаснаас нь Идэвхжүүлэх товчоор буцааж идэвхжүүлнэ. Идэвхжүүлмэгц хүлээгдэж байсан дахин илгээлтүүд үргэлжилнэ.
Webhook event-үүд
“Webhook event-үүд” хэсэгДараах төрлийн event-үүдийг бид одоогоор илгээж байна.
| Event | Тайлбар |
|---|---|
invoice.paid |
Нэхэмжлэх амжилттай төлөгдсөн. |
invoice.void |
Нэхэмжлэх хүчингүй болгогдсон. |
checkout.completed |
Checkout амжилттай төлөгдсөн. |
subscription.created |
Шинэ багц үүссэн. |
subscription.renewed |
Багцын мөчлөг сунгагдсан. |
subscription.updated |
Багц солигдсон, цуцлалт хүссэн эсвэл буцсан. |
subscription.renewal_due |
Сунгалтын сануулга илгээгдсэн. |
subscription.past_due |
Багцын хугацаа хэтэрсэн (grace period). |
subscription.canceled |
Багц бүрэн цуцлагдсан. |
product.stock_low |
Барааны үлдэгдэл цөөрсөн эсвэл дууссан. |
payment.awaiting_verification |
Худалдан авагч банкны шилжүүлэг хийснээ мэдэгдсэн; мерчант баталгаажуулах ёстой. |
payment.verification_due |
Баталгаажуулаагүй банкны шилжүүлгийн хүлээх хугацаа дуусахад 24 цаг үлдсэн. |
invoice.paid
“invoice.paid” хэсэгНэхэмжлэх амжилттай төлөгдсөн үед invoice.paid төрөлтэй event илгээх болно. data.object-т төлөгдсөн нэхэмжлэхийн объект очих болно.
{ "id": 3, "project_id": 1, "business_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.completed” хэсэгCheckout амжилттай төлөгдсөн үед checkout.completed төрөлтэй event илгээх болно. data.object-т төлөгдсөн checkout объект очих болно.
{ "id": 59, "project_id": 5, "business_id": 5, "type": "checkout.completed", "object": "checkout", "data": { "object": { "id": 13338, "project_id": 5, "mode": "payment", "status": "complete", "url": "https://byl.mn/h/checkout/13338/Yi7smBuk", "is_guest": false, "customer": { "id": 12, "client_reference_id": "user_842", "name": "Бат-Эрдэнэ" }, "amount_total": "27000.000000000000", "amount_subtotal": "30000.000000000000", "client_reference_id": "order_1042", "subscription_id": null, "items": [ { "product": { "id": 7, "name": "Starter багц", "client_reference_id": "starter" }, "price": { "id": 3, "type": "recurring", "unit_amount": 30000, "recurring_interval": "month", "recurring_interval_count": 1, "lookup_key": "starter_monthly" }, "quantity": 1, "amount_unit": 30000, "amount_subtotal": 30000, "amount_total": 30000, "adjustable_quantity": false } ], "phone_number_collection": true, "phone_number": "99999999", "email_collection": true, "delivery_address_collection": false, "delivery_address": null, "custom_fields": [ { "key": "company", "label": "Байгууллагын нэр", "type": "text", "optional": false, "value": "Кайтлаб ХХК" }, { "key": "seat", "label": "Суудал", "type": "dropdown", "optional": true, "dropdown": { "options": [ { "label": "VIP", "value": "vip" }, { "label": "Ердийн", "value": "erdiin" } ] }, "value": "vip" } ], "coupon_codes": [ { "code": "SUMMER2026", "coupon_name": "Зуны хямдрал", "discount_amount": "3000.00", "redeemed_at": "2026-08-14T09:30:00.000000Z" } ], "expires_at": "2026-08-14T16:00:00.000000Z", "created_at": "2026-08-14T09:12:00.000000Z", "updated_at": "2026-08-14T09:30:00.000000Z" } }, "created_at": "2026-08-14T09:30:00.000000Z", "updated_at": "2026-08-14T09:30:00.000000Z"}Анхаарах талбарууд:
customer— checkout-д харилцагч холбогдоогүй (guest) болnull.subscription_id— сунгалт эсвэл багц солих checkout үед л бөглөгдөнө. Шинээр багц үүсгэх checkout дээрnullбайх бөгөөд багц үүссэний дарааsubscription.createdevent тусад нь ирнэ.items[].price.lookup_key— үнэд өөрөө өгсөн код. Байхгүй болnull. Багцын логикооprice.id-аар биш үүгээр бичвэл үнээ дараа солиход код өөрчлөх шаардлагагүй.items[].price.idболонitems[].product.id— үнэ бүртгэлгүй (price_data-аар үүсгэсэн) item дээрnullбайна.amount_total,amount_subtotal— checkout дээр decimal тул тэмдэгт мөр (string), item дээр тоо байна.amount_subtotalнь хөнгөлөлт хасахаас өмнөх дүн,amount_totalнь хассаны дараах төлсөн дүн. Item-ийн дүнгүүд хөнгөлөлт хасагдаагүй байна.
coupon_codes талбар нь checkout-д хэрэглэгдсэн хөнгөлөлтийн кодуудын мэдээллийг агуулна. Хэрэв checkout-д хөнгөлөлтийн код хэрэглэгдээгүй бол энэ талбар хоосон массив байна.
custom_fields талбар нь checkout-д (төлбөрийн линк эсвэл API-аар) тодорхойлсон нэмэлт асуултуудыг худалдан авагчийн хариулттай нь (value) агуулна. Сонголт төрлийн value нь сонголтын label биш value байна; заавал биш талбарыг хоосон орхисон бол value нь null. Нэмэлт талбар тодорхойлоогүй бол хоосон массив байна.
coupon_codes массивын элемент бүр дараах талбаруудыг агуулна:
code: Хөнгөлөлтийн кодcoupon_name: Хөнгөлөлтийн нэрdiscount_amount: Хөнгөлөлтийн дүнredeemed_at: Хөнгөлөлт хэрэглэгдсэн огноо
subscription.*
“subscription.*” хэсэгБагцын төлөвт өөрчлөлт орох бүрд subscription. угтвартай event илгээгдэнэ (төрлүүдийг дээрх хүснэгтээс харна уу). data.object-т багцын объект харилцагч, бүтээгдэхүүн, үнийн мэдээллийн хамт очно.
{ "id": 87, "project_id": 1, "business_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": "Бат-Эрдэнэ", }, "product": { "id": 7, "name": "Starter багц", "client_reference_id": null }, "price": { "id": 3, "type": "recurring", "unit_amount": 30000, "recurring_interval": "month", "recurring_interval_count": 1, "lookup_key": "starter_monthly" }, "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 хуудаснаас уншина уу.
product.stock_low
“product.stock_low” хэсэгЗарах тоо тохируулсан бүтээгдэхүүний үлдэгдэл босго давах үед (5 ба доош, 0, эсвэл сөрөг болоход) product.stock_low төрөлтэй event илгээнэ. data.object-т бүтээгдэхүүний товч мэдээлэл болон үлдсэн тоо очно.
{ "id": 112, "project_id": 1, "business_id": 1, "type": "product.stock_low", "object": "product", "data": { "object": { "id": 7, "project_id": 1, "name": "Гар хийцийн ваар", "client_reference_id": null, "stock": 4 } }, "created_at": "2026-08-14T09:30:00.000000Z", "updated_at": "2026-08-14T09:30:00.000000Z"}Босго дотор давтагдсан борлуулалт бүрд дахин илгээхгүй — зөвхөн босго давах мөчид нэг удаа очно. Дэлгэрэнгүйг Барааны үлдэгдэл хуудаснаас уншина уу.
payment.awaiting_verification
“payment.awaiting_verification” хэсэгБанкны шилжүүлэг хэлбэрээр төлж буй худалдан авагч «Шилжүүлсэн» товч дарахад payment.awaiting_verification төрөлтэй event илгээнэ. data.object-т төлбөр, гүйлгээний код болон холбогдох checkout/нэхэмжлэх очно. Энэ event нь төлбөр төлөгдсөн гэсэн үг биш — мерчант дансны хуулгаа шалгаж баталгаажуулсны дараа ердийн checkout.completed / invoice.paid event илгээгдэнэ.
{ "id": 118, "project_id": 1, "business_id": 1, "type": "payment.awaiting_verification", "object": "payment", "data": { "object": { "id": "9d2f6c1e-1b1c-4a2e-9c7d-2d1f4a8b9e10", "project_id": 1, "status": "pending", "driver": "bank_transfer", "amount": 45000, "description": "Checkout - 231", "reference": "482913", "bank_name": "Хаан банк", "account_number": "5012345678", "claimed_at": "2026-08-27T09:30:00.000000Z", "expires_at": "2026-08-30T09:30:00.000000Z", "payable": { "type": "checkout", "id": 231, "url": "https://byl.mn/h/checkout/231/…", "status": "pending" }, "phone_number": "99112233", "is_test": false, "created_at": "2026-08-27T09:20:00.000000Z" } }, "created_at": "2026-08-27T09:30:00.000000Z", "updated_at": "2026-08-27T09:30:00.000000Z"}payable.type нь checkout эсвэл invoice байна (нэхэмжлэхийн хувьд number талбар нэмж очно).
Шилжүүлгийг удирдлагын булангаас эсвэл Bank transfers API-аар баталгаажуулна; platform баг харилцагчийн төслийн шилжүүлгийг ингэж өөрийн UI-аас баталгаажуулж болно.
payment.verification_due
“payment.verification_due” хэсэгМэдэгдсэн банкны шилжүүлэг баталгаажаагүй хэвээр байхад хүлээх хугацаа дуусахаас 24 цагийн өмнө payment.verification_due төрөлтэй event нэг удаа илгээнэ. data.object нь payment.awaiting_verification-тэй ижил бүтэцтэй; expires_at нь хүлээх хугацааны төгсгөл.