Direct Debit webhook payload
A Direct Debit form sends webhooks at three points: when the customer signs up, when the bank approves or rejects the mandate, and for every deduction after that. Each is an HTTP POST with a JSON body of event and data. This page lists every field.
For the full BCL API, see the API reference.
Set your webhook URL
Section titled “Set your webhook URL”You set the webhook on the Direct Debit form itself:
- Go to Payments > Forms and open your Direct Debit form.
- Click Advanced at the top, then open the Webhook Settings tab.
- Turn on Enable Webhook and enter one or more Webhook URLs (up to 10, each starting with
https://). - Under Webhook Events, tick the events you need. See the next section.
- Click Save Advanced Settings.
To try your endpoint, click Send Test and choose Direct Debit - When direct debit transactions occur. BCL sends three sample requests, one for each stage.
Which event fires when
Section titled “Which event fires when”The Direct Debit checkbox alone does not cover sign-ups. The four events map to the mandate like this:
| Stage | event |
Tick this event | record_type |
|---|---|---|---|
| Customer submits the form | form-submit |
Form Submit | DirectDebit |
| Bank approves the enrolment or a change | payment-success |
Payment Success | DirectDebit |
| Bank rejects it, or it fails | payment-failed |
Payment Failed | DirectDebit |
| Each deduction | direct-debit |
Direct Debit | DirectDebitDeduction |
BCL does not send a webhook when a mandate is terminated.
Sign-up and approval payload
Section titled “Sign-up and approval payload”The form-submit, payment-success and payment-failed events share one shape. This is a payment-success for an approved enrolment. The values are made up.
{ "event": "payment-success", "data": { "formable_type": "payment_form", "formable_id": 34, "form_title": "Monthly Fees", "form_url": "https://shop.example.com/form/monthly-fees", "form_featured_image": "", "record_type": "DirectDebit", "record_id": "LINK-00041", "main_data": { "id": "01jfmnrycszgmdhdk78nmtr3gs", "payer_name": "Ali bin Abu", "payer_email": "ali@example.com", "payer_telephone_number": "+60123456789", "order_number": "LINK-00041", "transaction_type": "live", "currency": "MYR", "subtotal_amount": "10.00", "fee_amount": "1.50", "amount": "11.50", "application_type": "Enrolment", "approval_status": "Approved", "bayarcash_mandate_id": "md_AbC123", "mandate_reference_number": "E-20261727400000", "effective_date": "2026-10-01", "frequency_mode": "MT", "created_at": "27/09/2026 10:15:00" }, "tracking_params": null, "custom_fields_data": { "company_name": "Contoh Trading" } }}The data object has these keys:
| Field | Type | Meaning |
|---|---|---|
formable_type |
string | Always payment_form. Replaces the old form_id. |
formable_id |
number | The form’s ID. |
form_title |
string | Form title. |
form_url |
string | Public link to the form, or empty. |
form_featured_image |
string | Featured image URL, or empty. |
record_type |
string | DirectDebit. |
record_id |
string | BCL order number of the mandate. It stays the same for every deduction. |
main_data |
object | The mandate. See below. |
tracking_params |
null | Not recorded for Direct Debit. |
custom_fields_data |
object | The form’s custom fields, keyed by field name. Absent if there are none. |
There is no receipt_url for Direct Debit.
In main_data, BCL leaves out fields that are empty:
| Field | Type | Meaning |
|---|---|---|
id |
string | Internal mandate ID. |
payer_name, payer_email, payer_telephone_number |
string | The customer’s details. |
order_number |
string | Same as record_id. |
transaction_type |
string | live or test. |
currency |
string | Usually MYR. |
subtotal_amount, fee_amount |
string | Amount before fees, and the fee. Always sent. |
amount |
string | Amount deducted each cycle. |
discount_amount, coupon_code, coupon_usage_id, additional_fee_amount, tax_amount, shipping, classification_code |
various | Sent only when the form uses them. Same meaning as on the payment form payload. |
application_type |
string | Approval events only: Enrolment or Maintenance (a change to the mandate). |
approval_status |
string | On approval events, one of New, Waiting Approval, Failed Bank Verification, Active, Terminated, Approved, Rejected, Cancelled or Error. On form-submit it is the raw status code, or absent. |
bayarcash_mandate_id |
string | Bayarcash mandate ID, once the bank has replied. |
mandate_reference_number |
string | Mandate reference from the bank. |
effective_date, expiry_date |
string | Mandate start and end dates. |
frequency_mode |
string | Deduction frequency. MT is monthly. |
created_at |
string | Sign-up time, DD/MM/YYYY HH:MM:SS, Malaysia time. |
items |
array | Products on the form, if any: index, item, option, quantity, unit_amount, amount, access_url, product_image and protected_content. |
Deduction payload
Section titled “Deduction payload”BCL sends direct-debit for every deduction result Bayarcash reports, successful or not. This payload still uses form_id.
{ "event": "direct-debit", "data": { "form_id": 34, "form_title": "Monthly Fees", "record_type": "DirectDebitDeduction", "record_id": "LINK-00041", "main_data": { "order_number": "1-727-400-448-141498", "payer_name": "Ali bin Abu", "payer_email": "ali@example.com", "payer_telephone_number": "+60123456789", "mandate_id": "md_AbC123", "mandate_reference_number": "E-20261727400000", "bayarcash_transaction_id": "trx_3qngpY", "batch_number": "HLB1727400000", "amount": "11.50", "status": 3, "status_description": "Successful", "datetime": "2026-10-01 00:00:00", "cycle": 1 }, "custom_fields_data": { "company_name": "Contoh Trading" } }}The deduction fields mean:
| Field | Type | Meaning |
|---|---|---|
form_id |
number | The form’s ID. |
form_title |
string | Form title. |
record_type |
string | DirectDebitDeduction. |
record_id |
string | BCL order number of the mandate. |
main_data.order_number |
string | Reference number of this deduction. |
payer_name, payer_email, payer_telephone_number |
string | The customer’s details from the mandate. |
mandate_id |
string | Bayarcash mandate ID. |
mandate_reference_number |
string | Mandate reference from Bayarcash. |
bayarcash_transaction_id |
string | Bayarcash transaction ID for this deduction. |
batch_number |
string or null | Bank batch number. |
amount |
string | Amount deducted. |
status |
number | Deduction status code. 3 means successful. |
status_description |
string or null | Status text from Bayarcash. |
datetime |
string | Deduction date and time. |
cycle |
number or null | Which deduction this is, counting from 1. |
custom_fields_data |
object | The form’s custom fields. Absent if there are none. |
Changes from the old sample
Section titled “Changes from the old sample”If you built your integration from the old sample, check these changes:
- Sign-up and approval payloads no longer have
form_id. Useformable_id. - New fields on sign-up and approval:
formable_type,form_url,form_featured_image,tracking_params,subtotal_amount,created_at, anditemswhen the form has products. - Rejected or failed approvals now arrive as
payment-failed. - The deduction payload is unchanged.
Check that a webhook is genuine
Section titled “Check that a webhook is genuine”BCL does not sign webhooks, so there is no signature to check. Before you act on one, look up the mandate or order through the BCL API, use a hard-to-guess URL, and make your handler safe to run twice for the same event. Every attempt is listed under Tools > Webhook Logs, where you can Resend it.
Common issues
I ticked Direct Debit but I do not get a webhook when a customer signs up. Why?
The Direct Debit event only covers deductions. The sign-up uses Form Submit, and the bank's approval or rejection uses Payment Success or Payment Failed. Tick all four to follow a mandate from start to finish.
How do I link a monthly deduction back to the customer's mandate?
In the direct-debit payload, record_id is the BCL order number of the mandate, the same record_id you received at sign-up. main_data.mandate_id is the Bayarcash mandate ID, which matches bayarcash_mandate_id in the approval webhook.
Why does the test webhook look different from the real one?
The Send Test sample for Direct Debit still uses form_id in the sign-up and approval payloads. Real sign-up and approval webhooks send formable_type and formable_id instead. Deduction webhooks, test and real, use form_id.
Was this article helpful?
Thanks for your feedback.
Sorry this didn't help. WhatsApp us and we'll sort it out with you.