Skip to content

Direct Debit webhook payload

Updated

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.

You set the webhook on the Direct Debit form itself:

  1. Go to Payments > Forms and open your Direct Debit form.
  2. Click Advanced at the top, then open the Webhook Settings tab.
  3. Turn on Enable Webhook and enter one or more Webhook URLs (up to 10, each starting with https://).
  4. Under Webhook Events, tick the events you need. See the next section.
  5. 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.

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.

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.

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.

If you built your integration from the old sample, check these changes:

  • Sign-up and approval payloads no longer have form_id. Use formable_id.
  • New fields on sign-up and approval: formable_type, form_url, form_featured_image, tracking_params, subtotal_amount, created_at, and items when the form has products.
  • Rejected or failed approvals now arrive as payment-failed.
  • The deduction payload is unchanged.

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?

Cookie settings

We use Google Analytics to see which guides help and where readers get stuck. It is on by default; you can turn it off. Your choice is saved on this device.