Payment form webhook payload
A webhook lets BCL tell your own system about a sale the moment it happens. When a customer submits a payment form, or their payment succeeds or fails, BCL sends an HTTP POST with a JSON body to each URL you set. This page lists every field in that body.
For the full BCL API, see the API reference.
Set your webhook URL
Section titled “Set your webhook URL”You set webhooks per form. To turn them on:
- Go to Payments > Forms and open your form.
- Click Advanced at the top, then open the Webhook Settings tab.
- Turn on Enable Webhook.
- Under Webhook URLs, enter your endpoint. Click Add Webhook URL to add more, up to 10. Every URL must start with
https://, and every URL receives the same events. - Under Webhook Events, tick the events you want.
- Click Save Advanced Settings.
To try your endpoint without a real sale, click Send Test in the Webhook Configuration section. Choose an Event Type, check the Webhook URL and the Payload Preview, then click Send Test Webhook. Test payloads use random sample values.
Event and booking forms have the same Webhook Settings tab under Advanced, with the same payment payload described here.
Events
Section titled “Events”Each request body has two keys: event (the event name) and data (the payload). These events use the payload on this page:
| Event | When BCL sends it |
|---|---|
form-submit |
The customer submits the form, before payment. |
payment-success |
Bayarcash confirms the payment. |
payment-failed |
The payment fails or is cancelled. |
For Direct Debit forms, see Direct Debit webhook payload. Event forms can also send ticket-checked-in, and booking forms booking-confirmed and booking-cancelled. Those use a different payload.
Sample payload
Section titled “Sample payload”This is a payment-success request. The values are made up.
{ "event": "payment-success", "data": { "formable_type": "payment_form", "formable_id": 210, "form_title": "Online Cooking Class", "form_url": "https://shop.example.com/form/online-cooking-class", "form_featured_image": "https://cdn.example.com/banner.jpg", "record_type": "Transaction", "record_id": "LINK-02046", "main_data": { "id": "01jfps609t08vjb8d5exq27bz6", "payer_name": "Ali bin Abu", "payer_email": "ali@example.com", "payer_telephone_number": "+60123456789", "order_number": "LINK-02046", "transaction_type": "live", "currency": "MYR", "subtotal_amount": "100.00", "fee_amount": "1.00", "amount": "101.00", "payment_channel": "FPX", "status": 3, "status_description": "Approved", "created_at": "27/09/2026 14:05:09", "customer_id": "01jfps5zq1m2k8c4x7v9b3n6td", "is_paid": 1, "retry_count": 0, "source_type": "payment_form", "items": [ { "id": 5501, "index": 101, "productable_type": "product_item", "productable_id": 101, "item": "Cooking Class Recording", "sku": "CC-001", "option": "", "quantity": 1, "unit_amount": "100.00", "normal_price": "120.00", "sale_price": "100.00", "amount": "100.00", "access_url": null, "weight": "0.00", "total_weight": "0.00", "metadata": null, "product_image": "https://cdn.example.com/class.jpg", "protected_content": [ { "title": "Module 1", "url": "https://shop.example.com/content/abc123token", "access_finder_url": "https://shop.example.com/content/7/access" } ] } ] }, "tracking_params": { "utm_source": "facebook", "utm_campaign": "september-promo" }, "receipt_url": "https://bcl.my/storage/pdf/LINK-02046.pdf", "custom_fields_data": { "address_line1": "1 Jalan Contoh", "city": "Kajang", "state": "Selangor", "postal_code": "43000", "country": "MY" }, "affiliate_data": { "referral_id": 57, "affiliate_id": 12, "affiliate_username": "siti", "affiliate_name": "Siti binti Ahmad", "affiliate_email": "siti@example.com", "affiliate_phone": "+60198765432", "commission_type": "percentage", "commission_rate": "10.00", "commission_amount": "10.00", "commission_breakdown": null, "status": "approved", "referral_source": "direct" } }}Top-level fields
Section titled “Top-level fields”The data object contains these keys:
| Field | Type | Meaning |
|---|---|---|
formable_type |
string | Form type: payment_form, event_form or booking_form. |
formable_id |
number | The form’s ID. Replaces the old form_id. |
form_title |
string | Form title. For event forms, the event name. |
form_url |
string | Public link to the form. Empty if the form has no slug or domain. |
form_featured_image |
string | Featured image URL, or empty. |
record_type |
string | Always Transaction on this page. |
record_id |
string | The BCL order number, for example LINK-02046. Use it to match records. |
main_data |
object | The order. See the next section. |
tracking_params |
object or null | UTM and click IDs captured when the customer opened the form: utm_source, utm_medium, utm_campaign, utm_content, utm_term, utm_id, fbclid, ttclid, gclid. Only the ones present are sent. |
receipt_url |
string | payment-success only. Link to the PDF receipt. Anyone opening it is asked to verify with a one-time code first. |
custom_fields_data |
object | The form’s custom fields and address fields, keyed by field name. Absent if there are none. |
affiliate_data |
object | Present only when an affiliate referred the sale. |
Order fields (main_data)
Section titled “Order fields (main_data)”BCL sends the order record and drops empty values. A field with no value (null, empty, or 0.00 for most amounts) is left out, so do not rely on every field being present.
| Field | Type | Meaning |
|---|---|---|
id |
string | Internal transaction ID. |
payer_name, payer_email, payer_telephone_number |
string | The buyer’s details. |
order_number |
string | Same as record_id. |
transaction_type |
string | live or test. |
currency |
string | Usually MYR. |
subtotal_amount |
string | Item total before fees. Always sent. |
discount_amount |
string | Coupon discount. |
coupon_code, coupon_usage_id |
string, number | The coupon used. |
fee_amount |
string | Fee charged to the buyer. Always sent. |
additional_fee_amount |
string | Extra fee set on the form. |
rounding_adjustment |
string | Rounding applied to the total. |
tax_amount |
object | Tax details, such as the tax name, rate and amount. |
shipping |
object | Shipping method name, cost and total_weight. |
bank_transfer_details |
object | Bank details, for manual transfer orders. |
amount |
string | Total the buyer pays. |
payment_channel |
string | Channel name, for example FPX, DuitNow QR, Credit Card, Cash on Delivery (COD) or Manual Bank Transfer. |
status |
number | Payment status code. 3 means successful and 2 means failed. It may be absent on form-submit. |
status_description |
string | Status text from Bayarcash. payment-success and payment-failed only. |
is_paid |
number | 1 once paid, otherwise 0. |
retry_count |
number | How many times the buyer retried payment. |
created_at |
string | Order time, DD/MM/YYYY HH:MM:SS, Malaysia time. |
customer_id |
string | BCL customer ID. |
classification_code |
string | e-invoice classification code. |
affiliate_id, cookie_id |
string | Affiliate attribution, when present. |
source_type |
string | Same as formable_type. |
items |
array | Items bought. See the next section. |
Item fields
Section titled “Item fields”Each entry in items has these keys:
| Field | Type | Meaning |
|---|---|---|
id |
number | Line item ID. |
index, productable_id |
number | Product ID. index is kept for older integrations. |
productable_type |
string | product_item, event_ticket or booking_service, for example. |
item, sku, option |
string | Product name, SKU and chosen variation. |
quantity |
number | Quantity. |
unit_amount, normal_price, sale_price |
string | Price paid per unit, normal price and sale price. |
amount |
string | Line total. |
access_url |
string or null | Download link for a digital product. |
weight, total_weight |
string | Weight per unit and for the line. |
metadata |
object or null | Extra item data. |
product_image |
string | Product image URL, or empty. |
protected_content |
array | payment-success only: each item has title, url (the buyer’s personal access link) and access_finder_url (the page where a buyer who lost the link can find it again). Empty on other events. |
Affiliate fields
Section titled “Affiliate fields”The affiliate_data object has referral_id, affiliate_id, affiliate_username, affiliate_name, affiliate_email and affiliate_phone. It also has commission_type (flat, percentage or per_item), commission_rate and commission_amount. For per-item commission, commission_rate is null and commission_breakdown lists each item. Finally, status (for example pending or approved) and referral_source.
Changes from the old sample
Section titled “Changes from the old sample”If you built your integration from the old sample, check these changes:
form_idis gone. Useformable_id, andformable_typefor the form type.- New top-level fields:
formable_type,form_url,form_featured_imageandtracking_params. - New order fields:
subtotal_amount,created_at,customer_id,is_paid,retry_count,source_typeand the discount, coupon, tax and shipping fields. - New item fields:
id,productable_type,productable_id,sku,normal_price,sale_price,weight,total_weight,metadata,product_imageandprotected_content. - New affiliate field:
commission_breakdown. - New event:
payment-failed.
Check that a webhook is genuine
Section titled “Check that a webhook is genuine”BCL does not sign webhooks. There is no signature header or shared secret, so anyone who knows your URL could post to it. To stay safe:
- Before you ship an order or grant access, look up the order by
record_idthrough the BCL API and confirm the status there. - Use a URL that is hard to guess, for example with a long random token in the path.
- Handle the same event more than once safely. Use
record_idandeventtogether to spot repeats.
Delivery and logs
Section titled “Delivery and logs”BCL sends each event once to each URL, one request per URL. Any 2xx response counts as delivered. Reply quickly and do the heavy work afterwards.
Every attempt is recorded under Tools > Webhook Logs, with the payload, your response and any error. To send one again, open the row’s menu and click Resend.
Common issues
I added my webhook URL but my system receives nothing. How do I test it?
Open the form's Webhook Settings tab and click Send Test. Pick an Event Type, check the Webhook URL and send. If the test arrives but real payments do not, check that you ticked Payment Success and not only Form Submit. If the test does not arrive, make sure your URL is public, starts with https://, accepts POST and replies with a 2xx status.
How can my own system know when a payment comes in?
Turn on the webhook for the form and tick Payment Success. BCL posts the order, buyer, items and custom fields to your URL as soon as the payment is confirmed. Match it to your records with record_id, which is the BCL order number.
Will importing old records fire my webhook?
Only if you tick Run webhooks and automations on the import page. Each imported row then triggers your webhooks as if it were a real payment, so leave it off if you only want the records in BCL.
Was this article helpful?
Thanks for your feedback.
Sorry this didn't help. WhatsApp us and we'll sort it out with you.