Host to Host Integration
Integrate FlexiBill directly into your business application or website using the API. Use this approach to programmatically create bills, register subscriptions, and manage billing without going through the dashboard.
Features & Benefits
🔗 Full Programmatic Control
Create bills and subscriptions directly from your application backend — ideal for customer onboarding flows that are fully integrated into your system.
📤 Multi-Channel Delivery via API
Deliver invoices to customers via Email and/or WhatsApp in a single API call, without any additional configuration on the dashboard.
🔁 Recurring and One-Time Support
The API supports both one-time bill creation and recurring billing with flexible cycle configuration.
How It Works
Step 1
Step 2
Step 3
Register and activate FlexiBill
Integrate the API into your application backend
Call the endpoint to create a bill or subscription — FlexiBill delivers the invoice to the customer automatically
API Reference
All API requests must include valid authentication headers. See the Authentication tab below.
All API requests require the following headers:
Client-Id
Your merchant Client ID from the DOKU Dashboard
Request-Id
A unique UUID per request
Request-Timestamp
UTC timestamp in ISO 8601 format
Signature
HMAC-SHA256 signature for request authentication
Example headers:
Client-Id: BRN-0228-1748939308074
Request-Id: 69e22f33-e7f3-49b9-9302-f0df1f6059b9
Request-Timestamp: 2025-06-16T03:32:26Z
Signature: HMACSHA256=WNdiEPJEZAW1vLthXygOkzMuCEadiEVK0saLjtFRqqs=👉 Learn more about how to generate signature
Create a new bill (one-time or recurring) that will be issued and delivered to a customer.
Endpoint:
Method
POST
POST
URL
https://api-sandbox.doku.com/bill-collection-core/v1/generate/invoice
https://api.doku.com/bill-collection-core/v1/generate/invoice
Request body:
{
"bill_identifier": "00001111122112",
"bill_title": "Gym Monthly Fee",
"bill_description": "Gym Fee April",
"bill_type": "ONE_TIME_BILL",
"amount": 10000,
"currency": "IDR",
"beneficiary_id": "TRIAL-00003",
"beneficiary_name": "John Smith",
"beneficiary_phone": "6287880777777",
"beneficiary_email": "john.smith@email.com",
"distribution_channel": ["EMAIL", "WHATSAPP"],
"invoice_term": "7 days",
"grace_period": "7 days",
"template_id_invoice": "TMP-240318-58821",
"template_invoice_param": [
"beneficiary_name",
"bill_identifier",
"bill_service_title",
"amount",
"payment_link"
],
"template_id_receipt": "TMP-RECEIPT-001",
"template_receipt_param": [
"beneficiary_name",
"bill_identifier",
"bill_service_title",
"receipt_link"
]
}For a recurring bill, add the schedule object:
Request parameters:
bill_identifier
✅
20
String
Unique bill reference ID. No spaces allowed.
bill_title
✅
256
String
Name of the billed service.
bill_description
Optional
256
String
Additional service description.
bill_type
✅
—
Enum
ONE_TIME_BILL or RECURRING
amount
✅
12
Double
Bill amount.
currency
✅
3
String
Currency code (e.g., IDR,MYR).
beneficiary_id
✅
20
String
Unique customer reference ID.
beneficiary_name
✅
128
String
Customer full name.
beneficiary_phone
✅
128
Numeric String
Customer phone with country code. Indonesia: 62, Malaysia: 60.
beneficiary_email
✅
256
String
Customer email address.
distribution_channel
✅
—
Array
One or both: EMAIL, WHATSAPP. Determines how the invoice and receipt are delivered to the customer.
invoice_term
Optional
128
String
Payment deadline window. Examples: due upon receipt, 7 days, end of month. Max 90 days.
grace_period
Optional
128
String
Buffer after due date. Examples: no grace period, 7 days. Max 90 days.
template_id_invoice
If WHATSAPP
—
String
WhatsApp template ID for invoice delivery. Required when distribution_channel includes WHATSAPP.
template_invoice_param
If WHATSAPP
—
Array
Ordered list of data fields mapped to the WhatsApp invoice template variables. Must match the variable order defined in the template. Required when template_id_invoice is provided.
template_id_receipt
If WHATSAPP
—
String
WhatsApp template ID for receipt delivery. Required when distribution_channel includes WHATSAPP.
template_receipt_param
If WHATSAPP
—
Array
Ordered list of data fields mapped to the WhatsApp receipt template variables. Must match the variable order defined in the template. Required when template_id_receipt is provided.
WhatsApp template parameters — template_id_invoice, template_invoice_param, template_id_receipt, and template_receipt_param are only required when distribution_channel includes WHATSAPP. If you are sending via EMAIL only, omit these four fields entirely. WhatsApp templates must be pre-approved and registered in the DOKU Dashboard before use via PAYCHAT
Schedule parameters (Recurring only):
schedule.bill_cycle.type
✅
Enum
—
INTERVAL — bills are issued at a fixed time interval. SPECIFIC_DATE — bills are issued on a set day each cycle.
schedule.bill_cycle.interval
If INTERVAL
String
{number}{unit}
Billing interval. Unit: d (day), m (month), y (year). Examples: 7d, 1m, 1y. Range: 1–999.
schedule.start_date
✅
String
ISO 8601 UTC
First bill issuance date. Must be ≥ request timestamp.
schedule.end_date.type
✅
Enum
—
AFTER_X_TIMES — stops after N cycles. SPECIFIC_DATE — stops on a calendar date. NEVER — runs until manually canceled.
schedule.end_date.count
If AFTER_X_TIMES
Integer
—
Number of billing cycles before the recurring bill stops. Must be > 0.
schedule.end_date.specific_date
If SPECIFIC_DATE
String
ISO 8601 UTC
The date the recurring bill ends. Must be ≥ start_date.
Response (HTTP 200):
Response parameters:
data.bill_identifier
Unique bill reference ID echoed from the request
data.bill_title
Name of the billed service
data.amount
Bill amount
data.currency
Currency code
data.beneficiary_id
Unique customer reference ID
data.beneficiary_name
Customer name
data.beneficiary_phone
Customer phone with country code
data.beneficiary_email
Customer email
data.state
Current invoice delivery state. See invoice delivery states below.
data.status
Payment status of the invoice. UNPAID on creation.
data.invoice_number
System-generated invoice number for this bill
Invoice delivery states (data.state):
INVOICE_IN_PROCESS
The system is generating the invoice.
INVOICE_CREATED
Invoice has been created and is queued for delivery.
INVOICE_SENT
Invoice has been successfully delivered to the customer.
INVOICE_ERROR
Delivery failed — typically due to insufficient deposit balance. Top up your deposit; the system will automatically retry.
Response Scenarios
HTTP 200 — Bill generated and invoice delivered.
What happens next:
The invoice is delivered to the customer via the channels specified in
distribution_channelThe bill is visible in the DOKU Dashboard under Subscription and Billing → Bill
Monitor payment status via Webhook or the dashboard
HTTP 400 — Unsupported or missing distribution_channel.
The distribution_channel array is empty, contains an invalid value, or WHATSAPP is specified but WhatsApp has not been activated for the merchant account.
Resolution: Use ["EMAIL"] if WhatsApp has not been activated. To enable WhatsApp delivery, activate DOKU PayChat from the DOKU Dashboard.
HTTP 400 — Missing template.
The template array is empty, contains an invalid value when WHATSAPP is specified as distribution channel.
Resolution: Use ["EMAIL"] if WhatsApp template has not been approved. Or check template has been approved in Paychat dashboard.
HTTP 400 — Recurring bill schedule is invalid.
The start_date is in the past, or the end_date configuration is inconsistent. Applies to bill_type: RECURRING only.
Common causes and resolution:
start_date is in the past
Set start_date to the current datetime or a future datetime
end_date.specific_date is before start_date
Ensure specific_date is ≥ start_date
bill_cycle.interval format is incorrect
Use {number}{unit} — e.g., 1m, 7d, 1y. Supported units: d (day), m (month), y (year). Range: 1–999
end_date.type is SPECIFIC_DATE but specific_date is missing
Include specific_date when type is SPECIFIC_DATE
HTTP 401 — Request authentication failed.
Resolution: Verify all required headers are present — Client-Id, Request-Id, Request-Timestamp, and Signature. The Request-Timestamp must be within an acceptable clock skew of the server time. → See the Authentication tab.
Register a customer subscription with the specified plan, schedule, and customer profile.
Endpoint:
Method
POST
POST
URL
https://api-sandbox.doku.com/subscription-core/v1/subscription
https://api.doku.com/subscription-core/v1/subscription
⚠️ Prerequisites
Before executing this API request, the following entities must be pre-registered via the DOKU Dashboard back-office. Passing unregistered codes will result in a validation error.
Collection
Subscription and Billing → Collection → Collection
collection_code
✅ Always
Plan
Subscription and Billing → Collection → Plan
plans[].code
✅ Always
Coupon
Subscription and Billing → Collection → Coupon
coupons[].code
If applying coupon
Tax
Subscription and Billing → Collection → Tax
taxes[].code
If applying tax
The collection_code, plans[].code, taxes[].code, and coupons[].code values must exactly match the codes registered in the dashboard. These codes are case-sensitive. There is no API endpoint to create collections, plans, taxes, or coupons — they must be configured in the dashboard first.
Request Body
Request Parameters
Top-level fields:
subscription_code
Optional
20
String
Unique subscription ID. Must be unique across the merchant account. Can be omitted if auto_generate_id is true. No spaces allowed.
auto_generate_id
Optional
—
Boolean
If true, the system auto-generates a unique subscription code. Ignored if subscription_code is provided.
plan_type
✅
—
Enum
SINGLE_PLAN or BUNDLING_PLAN
collection_code
✅
50
String
Code of the pre-registered collection. Must exist in the dashboard.
plans
✅
—
Array
At least 1 plan object required. Maximum 10 plans. All plans must belong to the same collection and share the same billing cycle.
plans[].code
✅
20
String
Code of the pre-registered plan. Must exist in the dashboard under the specified collection.
plans[].qty
✅
6
Integer
Quantity. Must be > 0. Relevant for Per Unit pricing plans.
plans[].amount
If open-amount plan
—
Decimal
Required only for Open Amount pricing plans. Ignored for all other pricing models.
coupons
Optional
—
Array
Discount coupons to apply. Pass the pre-registered coupon code.
coupons[].code
If coupons present
20
String
Code of the pre-registered coupon. Must exist in the dashboard, be within validity period, and have available stock.
taxes
Optional
—
Array
Taxes to apply. Pass the pre-registered tax code.
taxes[].code
If taxes present
20
String
Code of the pre-registered tax. Must exist in the dashboard under the specified collection.
currency
✅
3
String
ISO 4217 currency code (e.g., IDR, MYR).
total_amount
✅
—
Decimal
Grand total per billing cycle: plan price(s) minus coupon discount plus tax. For MYR must be ≥ 2.00. Must match the server-calculated total — a mismatch will return a validation error.
Schedule parameters (plan_schedule):
plan_schedule.start_date
✅
String
ISO 8601 UTC
Subscription activation datetime. Must be ≥ request timestamp. Cannot be a past date.
plan_schedule.end_date.type
✅
Enum
—
SPECIFIC_DATE — ends on a calendar date. AFTER_X_TIMES — ends after N billing cycles. NEVER — runs until manually canceled.
plan_schedule.end_date.specific_date
If SPECIFIC_DATE
String
ISO 8601 UTC
The date the subscription ends. Must be ≥ start_date.
plan_schedule.end_date.count
If AFTER_X_TIMES
Integer
—
Number of billing cycles before the subscription expires automatically. Must be > 0.
Customer parameters (customers[]):
customers[].id
Optional
20
String
Customer ID in the DOKU member database (Member Center). Provide this to link the subscription to an existing member record.
customers[].ext_id
Optional
20
String
Customer reference ID in the merchant's own system. Used for cross-referencing with internal records.
customers[].email
✅
100
String
Valid RFC 5322 email address. Invoices and receipts are delivered to this address.
customers[].name
✅
100
String
Customer full name.
customers[].phone_number
✅
15
String
Phone number excluding country calling code.
customers[].calling_code
✅
2
String
Country calling code: 62 (Indonesia), 60 (Malaysia).
customers[].additional_data
Optional
—
Object
Container for custom registration metadata (e.g., member group data, form responses).
Response Scenarios
HTTP 201 — Subscription created successfully.
The subscription has been registered. The first invoice will be issued automatically on plan_schedule.start_date.
What happens next:
The subscription is visible in the DOKU Dashboard under Subscription and Billing → Subscription
The first invoice is generated on the start date and delivered to the customer via the configured distribution channel
Subsequent invoices are issued automatically at each billing cycle
HTTP 400 — Collection, plan, tax, or coupon code not found.
One or more of the entity codes passed in the request do not exist in the dashboard, or do not belong to the specified collection. It can also happen when coupon is ineligible.
Common causes and resolution:
collection_code does not exist
Create the collection in the dashboard: Subscription and Billing → Collection → Collection
plans[].code does not exist
Create the plan in the dashboard: Subscription and Billing → Collection → Plan
plans[].code exists but belongs to a different collection
Verify the plan belongs to the collection passed in collection_code
taxes[].code does not exist
Create the tax in the dashboard: Subscription and Billing → Collection → Tax
coupons[].code does not exist or has expired
Create or reactivate the coupon in the dashboard: Subscription and Billing → Collection → Coupon
Code is correct but wrong casing
All codes are case-sensitive — verify the exact string including uppercase/lowercase
HTTP 400 — Schedule configuration is invalid.
The plan_schedule.start_date is in the past, or the end_date configuration is inconsistent.
Common causes and resolution:
start_date is in the past
Set start_date to the current datetime or a future datetime
end_date.specific_date is before start_date
Ensure specific_date is ≥ start_date
end_date.count is 0 or negative
Set count to a positive integer ≥ 1
end_date.type is SPECIFIC_DATE but specific_date is missing
Include specific_date when end_date.type is SPECIFIC_DATE
end_date.type is AFTER_X_TIMES but count is missing
Include count when end_date.type is AFTER_X_TIMES
HTTP 400 — total_amount does not match the server-calculated total.
The total_amount passed in the request does not match the amount calculated by the server based on the plan price(s), coupon discount, and tax rate.
Resolution: Recalculate total_amount using the following order before submitting:
Plan price(s) × qty → Coupon discount deducted → Subtotal → Tax applied on subtotal →
total_amount
Verify each component's value against the registered plan price, coupon discount value, and tax rate in the dashboard before resubmitting.
HTTP 400 — currency does not match the merchant's registered business country.
The currency value passed in the request is not valid for this merchant account. The accepted currency is determined by the country in which the merchant's business account is registered — it cannot be overridden per request.
Currency by registered business country:
🇮🇩 Indonesia
IDR
🇲🇾 Malaysia
MYR
Common causes and resolution:
Wrong currency code for business country
Use the currency that matches your merchant account's registered country — IDR for Indonesia, MYR for Malaysia
Currency code is lowercase or mixed case
currency is case-sensitive — use uppercase: "IDR" or "MYR", not "idr" or "Myr"
Attempting to use a third currency
Only the currency matching the registered business country is accepted. Multi-currency is not supported within a single merchant account
Your merchant account's registered business country is set at the time of KYB (Know Your Business) verification on DOKU Dashboard and cannot be changed without re-verification. If you need to process transactions in a different currency, a separate merchant account registered in the corresponding country is required.
HTTP 400 — Customer email or phone number format is invalid.
One or more fields in the customers[] object failed format validation. The subscription is not created.
Common causes and resolution:
Missing @ symbol — e.g., johnsmithatemail.com
Ensure the email contains exactly one @ separating local part and domain
Missing domain after @ — e.g., john@
Provide a complete domain — e.g., john@email.com
Missing local part before @ — e.g., @email.com
Provide the local part before @
Consecutive dots in domain — e.g., john@email..com
Remove duplicate dots in the domain portion
Space in address — e.g., john smith@email.com
Remove all spaces — email addresses do not allow spaces
Exceeds max length
Max 100 characters
Calling code included — e.g., phone_number: "6281234567890" with calling_code: "62"
Pass the calling code in calling_code only — e.g., phone_number: "81234567890", calling_code: "62"
Non-numeric characters — e.g., "+6281234567" or "0812-3456-7890"
Strip all non-numeric characters including +, -, spaces, and parentheses. Digits only
Unsupported value — e.g., calling_code: "1"
Accepted values: "62" (Indonesia) and "60" (Malaysia) only
Exceeds max length
Max 15 characters, excluding calling code
HTTP 404 — Merchant account is suspended.
The merchant account has been suspended by DOKU. No new transactions — subscriptions, bills, or any other — can be created until the suspension is lifted. The request payload is valid; the error is at the account level, not the request level.
Resolution:
This error cannot be resolved by modifying the request. Contact your DOKU Account Manager or DOKU Support directly to identify the cause of the suspension and the steps required to reinstate the account.
Do not retry the request repeatedly while the account is in suspended state — the request will continue to fail regardless of payload content until the account suspension is lifted by DOKU.
HTTP 401 — Request authentication failed.
The authentication headers are missing, malformed, or the signature does not match.
Resolution: Verify all required headers are present and correctly formed — Client-Id, Request-Id, Request-Timestamp, and Signature. The Request-Timestamp must be within an acceptable clock skew of the server time. Regenerate the signature if in doubt. → See the Authentication tab.
Use Cases
Description An internet service provider wants to integrate FlexiBill billing into their customer activation portal — when a customer activates a package, the system automatically creates a subscription and sends the first invoice. Solution Integrate the Create Subscription API into the activation portal backend. When a customer selects a package and submits, the system calls the FlexiBill API to create the subscription and deliver the first invoice programmatically. How It Works Customer selects package in portal → backend calls Create Subscription API → FlexiBill creates subscription and delivers first invoice → billing runs automatically each cycle. Features Used Create Subscription API, Generate Bill API, Webhook
Description A SaaS platform wants to bill customers based on the number of active users each month (per-seat billing). Solution At the end of each month, the SaaS system counts active users and calls the Generate Bill API with the dynamically calculated amount. How It Works System counts active users → calls Generate Bill API with calculated amount → FlexiBill delivers invoice to customer → customer pays via payment link. Features Used Generate Bill API (One-Time Bill), Webhook
Merchant & Customer Experience
Merchant View
Merchants (developers) integrate the API into their system backend:
Use the Sandbox endpoint for development and testing
Switch to the Production endpoint after testing is complete
Monitor bills and subscriptions created via API in the DOKU Dashboard just like any other billing
Customer View
The customer experience is identical to dashboard-initiated billing:
Receives invoice via Email or WhatsApp
Pays via the payment link
Receives receipt after successful payment
Terms & Conditions
Merchant must be registered with a corporate business account on DOKU Dashboard
Business must be KYB verified before using the production API
All requests must use HTTPS and include valid authentication headers
FAQ
What is the difference between the Sandbox and Production endpoints?
The Sandbox endpoint is used for development and testing — no real transactions are processed and no invoices are delivered to actual customers. The Production endpoint processes live transactions and delivers real invoices. Always develop and test in Sandbox first, then switch the base URL to Production when going live. Your Client-Id and Client-Secret are different between environments — use the correct credentials for each.
Do bills and subscriptions created via API appear in the DOKU Dashboard?
Yes. All bills and subscriptions created via the API are immediately visible in the DOKU Dashboard — under Subscription and Billing → Bill and Subscription and Billing → Subscription respectively — and are manageable the same way as records created through the dashboard.
How do I obtain my Client-Id and generate the Signature?
Your Client-Id is available in the DOKU Dashboard under the Integrations menu. The Signature is an HMAC-SHA256 hash generated from your Client-Secret and a canonical request string composed of the request headers and body. Full instructions for signature generation are available in the DOKU API documentation.
The collection, plan, or coupon code I am passing is returning a validation error. What should I check?
All entity codes (collection_code, plans[].code, taxes[].code, coupons[].code) must be pre-registered in the DOKU Dashboard before they can be referenced via the API. There is no API endpoint to create these entities programmatically. Check the following in order:
The entity exists in the dashboard under the correct menu path
The code string is an exact match — these values are case-sensitive
For plans: the plan belongs to the collection specified in
collection_codeFor coupons: the coupon is active, within its validity period, and has available stock
For taxes: the tax is associated with the collection specified in
collection_code
How should I calculate `total_amount` before submitting the Create Subscription request?
Calculate total_amount using the following order:
Plan price(s) × qty → Coupon discount deducted → Subtotal → Tax rate applied on subtotal →
total_amount
The value you send must match the server-calculated total exactly. A mismatch will return a INVALID_REQUEST error with field: total_amount. Verify the registered plan price, coupon discount value (flat or percentage), and tax rate in the dashboard before computing.
Can I pass both `subscription_code` and `auto_generate_id: true` in the same request?
Yes, but the subscription_code takes precedence. When subscription_code is present, the system uses that value and ignores auto_generate_id. Only omit subscription_code entirely when you want the system to auto-generate one. The generated code is not returned in the response body — look it up in the dashboard or via webhook if you need it.
What happens if I submit a Create Subscription request with a `start_date` in the past?
The request will return an INVALID_REQUEST error with field: plan_schedule.start_date. The start_date must be greater than or equal to the request timestamp. Past dates are not accepted. If you need the subscription to start immediately, set start_date to the current UTC time.
My Generate Bill request returned `state: INVOICE_ERROR`. What should I do?
INVOICE_ERROR means the invoice was created but delivery to the customer failed — the most common cause is insufficient deposit balance in your DOKU merchant account. Top up your deposit balance from the DOKU Dashboard. Once balance is restored, the system will automatically retry delivery. You can also monitor the current state of the invoice from the dashboard under Subscription and Billing → Bill.
How do I handle duplicate requests safely for Generate Bill?
The bill_identifier field acts as an idempotency key — the same identifier cannot be used twice within a merchant account. If your request times out or you are unsure whether it was processed, do not immediately retry with the same bill_identifier. First query the bill's status via the dashboard or inquiry endpoint to confirm whether the original request succeeded. Only generate a new bill (with a new identifier) if you have confirmed the original was not processed.
What is the difference between Generate Bill and Create Subscription for recurring billing?
Both support recurring billing, but they serve different purposes:
Generate Bill (RECURRING) — You define the schedule and the system handles recurring invoice issuance. The billing configuration (amount, cycle, customer) is passed directly in the API call with no dependency on dashboard-configured entities. Use this for custom, ad-hoc, or dynamically-calculated billing where amounts may vary per cycle.
Create Subscription — The subscription is bound to a pre-registered Collection and Plan. Pricing, billing cycle, tax, and coupon are all defined in the dashboard. Use this for structured product/service offerings where the plan configuration is reusable and managed centrally.
How do I know when a customer has paid their invoice?
Set up a Webhook to receive real-time payment notifications. When a customer completes payment, FlexiBill sends a PAID event to your configured webhook endpoint with the invoice number and payment details. You can also poll the bill status from the dashboard. → Webhook
Can I cancel or modify a subscription created via the API?
Subscriptions created via the API can be viewed and managed from the DOKU Dashboard. Cancellation is performed from the dashboard — there is no dedicated cancel subscription API endpoint. Plan, collection, and schedule fields cannot be changed after a subscription is created. To change the plan for a customer, cancel the existing subscription from the dashboard and create a new one via the API with the updated configuration.
Last updated
Was this helpful?
