### Webhook Handler Requirements and Examples Source: https://docs.bayar.digital/webhook This section outlines the essential criteria for your webhook endpoint to ensure reliable processing of payment notifications from Bayar Digital. It includes security verification, data validation, idempotency, and prompt responses. Code examples are provided for Node.js/Express, PHP/Laravel, and Python/FastAPI. ```APIDOC ## POST /webhooks/bayar ### Description This endpoint receives asynchronous payment status updates from Bayar Digital. It is crucial to implement security checks, validate payment details, and ensure idempotent processing of these notifications. ### Method POST ### Endpoint `/webhooks/bayar` ### Parameters #### Request Body - **payment_id** (string) - The unique identifier for the payment. - **payment_code** (string) - The payment code associated with the transaction. Must be validated against your system's records. - **status** (string) - The current status of the payment (e.g., PAID, EXPIRED, CANCELLED). Must be one of the accepted values. - **amount** (integer) - The amount of the payment. Must be validated against the recorded payment total. ### Request Example (Node.js / Express) ```javascript app.post('/webhooks/bayar', express.json(), (req, res) => { // 1. Verify signature if (process.env.WEBHOOK_SECRET) { const expected = crypto .createHmac('sha256', process.env.WEBHOOK_SECRET) .update(JSON.stringify(req.body)) .digest('hex'); if (req.headers['x-signature'] !== expected) { return res.status(401).json({ status: 'invalid signature' }); } } const { payment_id, payment_code, status, amount } = req.body; // 2. Find order in database // 3. Validate amount // 4. Update status (ensure idempotent handling) res.json({ status: 'received' }); }); ``` ### Request Example (PHP / Laravel) ```php Route::post('webhooks/bayar', function (Request $request) { $secret = config('services.bayar.webhook_secret'); if ($secret) { $expected = hash_hmac('sha256', $request->getContent(), $secret); if (!hash_equals($expected, $request->header('X-Signature', ''))) { return response()->json(['status' => 'invalid signature'], 401); } } $data = $request->validate([ 'payment_code' => 'required|string', 'status' => 'required|in:PAID,EXPIRED,CANCELLED', 'amount' => 'required|integer', ]); // Find order in database, validate amount, and update status (idempotent) return response()->json(['status' => 'received']); }); ``` ### Request Example (Python / FastAPI) ```python @app.post("/webhooks/bayar") async def webhook(request: Request): body = await request.body() secret = os.environ.get("WEBHOOK_SECRET") if secret: expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, request.headers.get("x-signature", "")): raise HTTPException(status_code=401, detail="invalid signature") data = await request.json() # Find order in database, validate amount, and update status (idempotent) return {"status": "received"} ``` ### Handler Requirements 1. **Verify Signature:** Perform security verification if using a Webhook Secret. 2. **Validate `payment_code`:** Ensure the code is registered in your system. 3. **Validate `amount`:** Match the webhook's `amount` with the `payment_total` in your database. 4. **Be Idempotent:** Ensure the system is safe even if it receives the same webhook multiple times. 5. **Respond with `200 OK` ASAP:** Do not hold the connection. If data update is time-consuming, queue the webhook payload, respond `200 OK` to Bayar Digital, and process updates asynchronously. ``` -------------------------------- ### Get Channel Instructions Source: https://docs.bayar.digital/channel-instructions Fetches the payment instructions for a given bank channel ID. This information is intended to be displayed directly to end-users in your application. ```APIDOC ## GET /gateway/channels/{channel_id}/instructions ### Description Retrieves the payment instructions for a specific bank channel. This data can be directly presented to users in your application interface. ### Method GET ### Endpoint `https://api.bayar.digital/gateway/channels/{channel_id}/instructions` ### Parameters #### Path Parameters - **channel_id** (uuid) - Required - ID of the channel (obtained from the `GET /gateway/accounts` response). #### Query Parameters None #### Request Body This request does not have a request body. ### Request Example ```curl https://api.bayar.digital/gateway/channels/660e8400-e29b-41d4-a716-446655440001/instructions \ -H "X-Api-Key: pk_..." ``` ### Response #### Success Response (200 OK) - **channel_id** (uuid) - ID of the related channel. - **instructions** (array) - A list of sequential payment steps. **Format Object in `instructions`** - **step** (int) - The sequence number of the step. - **title** (string) - The title or summary of the step. - **content** (string) - The detailed explanation of the step. #### Response Example ```json { "success": true, "message": "ok", "data": { "channel_id": "660e8400-e29b-41d4-a716-446655440001", "instructions": [ { "step": 1, "title": "Buka aplikasi BCA Mobile", "content": "Login ke aplikasi BCA Mobile Anda" }, { "step": 2, "title": "Pilih m-Transfer", "content": "Pilih menu m-Transfer > ke Rekening BCA Virtual Account" }, { "step": 3, "title": "Masukkan nominal", "content": "Transfer sesuai total yang tertera" } ] } } ``` ### Error Handling #### 400 Bad Request - **channel_id is required**: The `channel_id` parameter was not included in the URL. ```json { "success": false, "code": "channel_id is required", "message": "channel id is required" } ``` #### 403 Forbidden - **tenant_api_key_required**: The API Key is invalid or not included. #### 500 Internal Server Error - **internal_error**: A server issue occurred, please try again in a moment. ``` -------------------------------- ### Successful Response for Channel Instructions Source: https://docs.bayar.digital/channel-instructions This is an example of a successful response when fetching payment instructions. It includes the channel ID and a list of sequential steps with titles and detailed content. ```json { "success": true, "message": "ok", "data": { "channel_id": "660e8400-e29b-41d4-a716-446655440001", "instructions": [ { "step": 1, "title": "Buka aplikasi BCA Mobile", "content": "Login ke aplikasi BCA Mobile Anda" }, { "step": 2, "title": "Pilih m-Transfer", "content": "Pilih menu m-Transfer > ke Rekening BCA Virtual Account" }, { "step": 3, "title": "Masukkan nominal", "content": "Transfer sesuai total yang tertera" } ] } } ``` -------------------------------- ### Successful Payment Detail Response (200 OK) Source: https://docs.bayar.digital/payment-detail This is an example of a successful response when retrieving payment details. It includes all relevant information about the invoice. ```json { "success": true, "message": "ok", "data": { "payment_id": "660e8400-e29b-41d4-a716-446655440010", "payment_code": "INV-2026-0001", "payment_amount": 50000, "payment_unique": 123, "payment_total": 50123, "payment_status": "PAID", "payment_expired_at": "2026-10-11T12:00:00Z", "payment_updated_at": "2026-06-11T10:05:00Z", "payment_webhook_url": "https://yourserver.com/webhooks/bayar", "payment_checkout_url": "https://bayar.digital/checkout/660e8400-e29b-41d4-a716-446655440010", "payment_return_url": "https://yourserver.com/orders/INV-2026-0001", "customer_name": "Budi Santoso", "customer_email": "budi@example.com", "customer_phone": "081234567890", "customer_orders": [ { "name": "Produk A", "price": 50000, "quantity": 1, "subtotal": 50000 } ], "account_id": "550e8400-e29b-41d4-a716-446655440000", "account_number": "1234567890", "account_name": "PT Merchant Contoh", "channel_id": "660e8400-e29b-41d4-a716-446655440001", "channel_name": "Bank Central Asia", "channel_type": "TRANSFER", "channel_instructions": [], "is_manual_match": false, "manual_matched_mutation_id": null } } ``` -------------------------------- ### Webhook Payload Example Source: https://docs.bayar.digital/webhook This is the general structure of the JSON payload sent to your server for webhook notifications. ```json { "payment_id": "660e8400-e29b-41d4-a716-446655440010", "payment_code": "INV-2026-0001", "status": "PAID", "amount": 50123, "paid_at": "2026-06-11T10:05:00Z" } ``` -------------------------------- ### Node.js/Express Webhook Handler Source: https://docs.bayar.digital/webhook Example of a Node.js Express route to handle incoming webhooks. It includes signature verification and a placeholder for order processing. ```javascript app.post('/webhooks/bayar', express.json(), (req, res) => { // 1. Verifikasi signature if (process.env.WEBHOOK_SECRET) { const expected = crypto .createHmac('sha256', process.env.WEBHOOK_SECRET) .update(JSON.stringify(req.body)) .digest('hex'); if (req.headers['x-signature'] !== expected) { return res.status(401).json({ status: 'invalid signature' }); } } const { payment_id, payment_code, status, amount } = req.body; // 2. Cari order di database // 3. Validasi amount // 4. Update status (pastikan penanganan bersifat idempotent) res.json({ status: 'received' }); }); ``` -------------------------------- ### Python/FastAPI Webhook Handler Source: https://docs.bayar.digital/webhook Example of a Python FastAPI endpoint for handling webhooks. It includes signature verification using HMAC and a placeholder for order processing. ```python @app.post("/webhooks/bayar") async def webhook(request: Request): body = await request.body() secret = os.environ.get("WEBHOOK_SECRET") if secret: expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, request.headers.get("x-signature", "")): raise HTTPException(status_code=401, detail="invalid signature") data = await request.json() # Cari order di database, validasi amount, dan update status (idempotent) return {"status": "received"} ``` -------------------------------- ### GET /gateway/mutations Source: https://docs.bayar.digital/payment-mutations Retrieves a list of incoming transaction mutations. It can be filtered to show only unmatched transactions. ```APIDOC ## GET /gateway/mutations ### Description Retrieves a list of incoming transaction mutations detected by the Android Worker. This data is useful for reconciliation and manual transaction matching. ### Method GET ### Endpoint https://api.bayar.digital/gateway/mutations ### Parameters #### Query Parameters - **page** (integer) - Optional - The page number of the data to display. Defaults to 1. - **per_page** (integer) - Optional - The number of data entries to retrieve per page (maximum 100). Defaults to 20. - **only_unmatched** (bool) - Optional - Filter to display only transactions that are not yet matched. Defaults to `false`. ### Headers #### Required Headers - **X-Api-Key** (string) - Required - Your merchant API Key. ### Request Body This request does not have a request body. ### Request Example ```curl https://api.bayar.digital/gateway/mutations?only_unmatched=true \ -H "X-Api-Key: pk_..." ``` ### Response #### Success Response (200 OK) - **id** (uuid) - Unique ID of the mutation. - **device_id** (uuid) - ID of the Android Worker device that detected the transaction. - **title** (string/null) - Title of the transfer notification from the bank app. - **body** (string/null) - Detailed content of the transfer notification. - **posted_at** (datetime) - The time the transaction was recorded by the bank. - **amount_parsed** (int64) - The successfully extracted transfer amount. - **sender_parsed** (string/null) - The successfully extracted sender's name. - **type_parsed** (string/null) - Mutation type: `CREDIT` (incoming funds) / `DEBIT` (outgoing funds). - **currency_parsed** (string/null) - Currency (e.g., `IDR`). - **is_matched** (bool) - Indicator if this mutation has been matched with an invoice. - **matched_payment_id** (uuid/null) - ID of the matched invoice. - **matched_payment_code** (string/null) - Code of the matched invoice. - **created_at** (datetime) - Time the mutation was detected and recorded by the server system. - **device_name** (string/null) - Name of the Android Worker device. - **package_name** (string/null) - Package name of the bank application that sent the notification. - **app_name** (string/null) - Name of the bank application sender. - **app_version** (string/null) - Version of the bank application sender. - **account_number** (string/null) - Destination account number for the funds. - **account_name** (string/null) - Name of the destination account owner. - **bank_name** (string/null) - Name of the recipient bank. - **bank_type** (string/null) - Method type: `TRANSFER` or `QRIS`. #### Response Example (200 OK) ```json { "success": true, "message": "ok", "data": [ { "id": "550e8400-e29b-41d4-a716-446655440020", "device_id": "660e8400-e29b-41d4-a716-446655440030", "title": "Transfer dari BUDI SANTOSO", "body": "BCA ke 1234567890 a/n PT Merchant Contoh", "posted_at": "2026-06-11T10:30:00Z", "amount_parsed": 50123, "sender_parsed": "BUDI SANTOSO", "type_parsed": "CREDIT", "currency_parsed": "IDR", "is_matched": false, "matched_payment_id": null, "matched_payment_code": null, "created_at": "2026-06-11T10:30:05Z", "device_name": "HP Kantor", "package_name": "com.bca", "app_name": "BCA Mobile", "app_version": "6.2.0", "account_number": "1234567890", "account_name": "PT Merchant Contoh", "bank_name": "Bank Central Asia", "bank_type": "TRANSFER" } ], "pagination": { "total": 50, "count": 1, "per_page": 20, "current_page": 1, "total_pages": 3 } } ``` #### Error Response (403 Forbidden) - **Code**: `tenant_api_key_required` - **Status**: 403 - **Description**: API Key is invalid or not provided. #### Error Response Example (403 Forbidden) ```json { "success": false, "code": "tenant_api_key_required", "message": "API Key is required" } ``` #### Error Response (500 Internal Server Error) - **Code**: `internal_error` - **Status**: 500 - **Description**: An error occurred on the server, please try again in a moment. #### Error Response Example (500 Internal Server Error) ```json { "success": false, "code": "internal_error", "message": "internal server error" } ``` ``` -------------------------------- ### Successful Payment List Response Source: https://docs.bayar.digital/payment-list This is an example of a successful response (200 OK) when retrieving the payment list. It includes details about the payments and pagination information. ```json { "success": true, "data": [ { "payment_id": "660e8400-e29b-41d4-a716-446655440010", "payment_code": "INV-2026-0001", "payment_amount": 50000, "payment_unique": 123, "payment_total": 50123, "payment_status": "PAID", "payment_expired_at": "2026-10-11T12:00:00Z", "payment_updated_at": "2026-06-11T10:05:00Z", "payment_webhook_url": "https://yourserver.com/webhooks/bayar", "payment_checkout_url": "https://bayar.digital/checkout/660e8400-e29b-41d4-a716-446655440010", "payment_return_url": "https://yourserver.com/orders/INV-2026-0001", "customer_name": "Budi Santoso", "customer_email": "budi@example.com", "customer_phone": "081234567890", "customer_orders": [], "account_id": "550e8400-e29b-41d4-a716-446655440000", "account_number": "1234567890", "account_name": "PT Merchant Contoh", "channel_id": "660e8400-e29b-41d4-a716-446655440001", "channel_name": "Bank Central Asia", "channel_type": "TRANSFER", "channel_instructions": [], "is_manual_match": false, "manual_matched_mutation_id": null } ], "pagination": { "total": 50, "count": 1, "per_page": 20, "current_page": 1, "total_pages": 3 } } ``` -------------------------------- ### Get Accounts Source: https://docs.bayar.digital/ Retrieve a list of destination bank accounts available for transactions. ```APIDOC ## GET /gateway/accounts ### Description Retrieves a list of destination bank accounts for payment processing. ### Method GET ### Endpoint /gateway/accounts ### Parameters #### Query Parameters None #### Request Body None ### Request Example None ### Response #### Success Response (200) - **accounts** (array) - A list of available bank accounts. #### Response Example { "accounts": [ { "bank_name": "BCA", "account_number": "1234567890", "account_name": "PT. BAYAR DIGITAL" } ] } ``` -------------------------------- ### Get Payment Detail Request Source: https://docs.bayar.digital/payment-detail Use this cURL command to retrieve the details of a specific payment using its payment code and your API key. ```bash curl https://api.bayar.digital/gateway/payments/INV-2026-0001 \ -H "X-Api-Key: pk_..." ``` -------------------------------- ### Internal Server Error Response Source: https://docs.bayar.digital/payment-mutations This example shows the response format for a 500 Internal Server Error. It indicates a problem on the server side. ```JSON { "success": false, "code": "internal_error", "message": "internal server error" } ``` -------------------------------- ### Verify Webhook Signature in Node.js Source: https://docs.bayar.digital/webhook Example function to verify the webhook signature using Node.js crypto module. Ensure the secret is kept secure. ```javascript const crypto = require('crypto'); function verifyWebhook(body, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(JSON.stringify(body)) .digest('hex'); return expected === signature; } ``` -------------------------------- ### Webhook Signature Header Example Source: https://docs.bayar.digital/webhook The X-Signature header contains a hex-encoded HMAC-SHA256 hash of the raw JSON body, used for verification. ```text X-Signature: ``` -------------------------------- ### CANCELLED Status Webhook Payload Source: https://docs.bayar.digital/webhook Example of a webhook payload when the payment status is CANCELLED. Does not include the 'paid_at' timestamp. ```json { "payment_id": "660e8400-e29b-41d4-a716-446655440010", "payment_code": "INV-2026-0001", "status": "CANCELLED", "amount": 50123 } ``` -------------------------------- ### GET /gateway/payments Source: https://docs.bayar.digital/payment-list Retrieves a list of all invoices, sorted from the most recently created. Supports pagination via `page` and `per_page` query parameters. ```APIDOC ## GET /gateway/payments ### Description Retrieves a list of all invoices, sorted from the most recently created. This endpoint is useful for bulk data reconciliation or displaying transaction history. ### Method GET ### Endpoint https://api.bayar.digital/gateway/payments ### Parameters #### Query Parameters - **page** (integer) - Optional - Halaman data yang ingin ditampilkan. Default is 1. - **per_page** (integer) - Optional - Jumlah data yang ditarik per halaman. Default is 20, maximum is 100. #### Header Parameters - **X-Api-Key** (string) - Required - API Key merchant Anda. ### Request Example ```curl curl "https://api.bayar.digital/gateway/payments?page=1&per_page=20" \ -H "X-Api-Key: pk_..." ``` ### Response #### Success Response (200 OK) - **total** (int) - Total keseluruhan data yang ada. - **count** (int) - Jumlah data yang ditarik pada halaman saat ini. - **per_page** (int) - Batas maksimal data per halaman. - **current_page** (int) - Posisi halaman saat ini. - **total_pages** (int) - Total keseluruhan halaman yang tersedia. - **data** (array) - Array of payment objects. - **payment_id** (string) - Unique identifier for the payment. - **payment_code** (string) - The invoice code. - **payment_amount** (integer) - The base amount of the payment. - **payment_unique** (integer) - The unique amount added to the payment. - **payment_total** (integer) - The total amount including the unique amount. - **payment_status** (string) - The status of the payment (e.g., PAID, UNPAID, EXPIRED). - **payment_expired_at** (string) - The expiration date and time of the payment in ISO 8601 format. - **payment_updated_at** (string) - The last updated date and time of the payment in ISO 8601 format. - **payment_webhook_url** (string) - The URL for receiving webhook notifications. - **payment_checkout_url** (string) - The URL to the payment checkout page. - **payment_return_url** (string) - The URL to redirect the user after payment. - **customer_name** (string) - The name of the customer. - **customer_email** (string) - The email of the customer. - **customer_phone** (string) - The phone number of the customer. - **customer_orders** (array) - List of orders associated with the customer. - **account_id** (string) - The ID of the bank account. - **account_number** (string) - The bank account number. - **account_name** (string) - The name of the bank account holder. - **channel_id** (string) - The ID of the payment channel. - **channel_name** (string) - The name of the payment channel. - **channel_type** (string) - The type of the payment channel (e.g., TRANSFER, VIRTUAL_ACCOUNT). - **channel_instructions** (array) - Instructions for the payment channel. - **is_manual_match** (boolean) - Indicates if the payment was manually matched. - **manual_matched_mutation_id** (string|null) - The ID of the mutation if manually matched. #### Response Example (200 OK) ```json { "success": true, "data": [ { "payment_id": "660e8400-e29b-41d4-a716-446655440010", "payment_code": "INV-2026-0001", "payment_amount": 50000, "payment_unique": 123, "payment_total": 50123, "payment_status": "PAID", "payment_expired_at": "2026-10-11T12:00:00Z", "payment_updated_at": "2026-06-11T10:05:00Z", "payment_webhook_url": "https://yourserver.com/webhooks/bayar", "payment_checkout_url": "https://bayar.digital/checkout/660e8400-e29b-41d4-a716-446655440010", "payment_return_url": "https://yourserver.com/orders/INV-2026-0001", "customer_name": "Budi Santoso", "customer_email": "budi@example.com", "customer_phone": "081234567890", "customer_orders": [], "account_id": "550e8400-e29b-41d4-a716-446655440000", "account_number": "1234567890", "account_name": "PT Merchant Contoh", "channel_id": "660e8400-e29b-41d4-a716-446655440001", "channel_name": "Bank Central Asia", "channel_type": "TRANSFER", "channel_instructions": [], "is_manual_match": false, "manual_matched_mutation_id": null } ], "pagination": { "total": 50, "count": 1, "per_page": 20, "current_page": 1, "total_pages": 3 } } ``` #### Error Response (403 Forbidden) ```json { "success": false, "code": "tenant_api_key_required", "message": "API Key tidak valid atau tidak disertakan" } ``` #### Error Response (500 Internal Server Error) ```json { "success": false, "code": "internal_error", "message": "internal server error" } ``` ``` -------------------------------- ### PHP/Laravel Webhook Handler Source: https://docs.bayar.digital/webhook Example of a PHP Laravel route to handle incoming webhooks. It demonstrates signature verification and data validation using Laravel's request validation. ```php Route::post('webhooks/bayar', function (Request $request) { $secret = config('services.bayar.webhook_secret'); if ($secret) { $expected = hash_hmac('sha256', $request->getContent(), $secret); if (!hash_equals($expected, $request->header('X-Signature', ''))) { return response()->json(['status' => 'invalid signature'], 401); } } $data = $request->validate([ 'payment_code' => 'required|string', 'status' => 'required|in:PAID,EXPIRED,CANCELLED', 'amount' => 'required|integer', ]); // Cari order di database, validasi amount, dan update status (idempotent) return response()->json(['status' => 'received']); }); ``` -------------------------------- ### EXPIRED Status Webhook Payload Source: https://docs.bayar.digital/webhook Example of a webhook payload when the payment status is EXPIRED. Does not include the 'paid_at' timestamp. ```json { "payment_id": "660e8400-e29b-41d4-a716-446655440010", "payment_code": "INV-2026-0001", "status": "EXPIRED", "amount": 50123 } ``` -------------------------------- ### Signature Verification Source: https://docs.bayar.digital/webhook This section explains how to verify the authenticity of incoming webhook requests from Bayar Digital using a signature header. It provides code examples in Node.js, PHP, and Python. ```APIDOC ## Signature Verification ### Description If you have set a **Webhook Secret** in the Dashboard, every webhook request from Bayar Digital will include an additional security header: `X-Signature`. This signature is calculated using the HMAC-SHA256 method on the raw JSON body with your Webhook Secret, ensuring the request originates from Bayar Digital. ### How to Verify **Example (Node.js)** ```javascript const crypto = require('crypto'); function verifyWebhook(body, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(JSON.stringify(body)) .digest('hex'); return expected === signature; } ``` **Example (PHP)** ```php function verifyWebhook($body, $signature, $secret) { $expected = hash_hmac('sha256', json_encode($body), $secret); return hash_equals($expected, $signature); } ``` **Example (Python)** ```python import hmac, hashlib, json def verify_webhook(body, signature, secret): expected = hmac.new( secret.encode(), json.dumps(body, separators=(',', ':')).encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature) ``` ``` -------------------------------- ### Successful Payment Account Response Source: https://docs.bayar.digital/payment-account This is an example of a successful response when retrieving payment accounts. It includes details for both bank transfer and QRIS accounts. ```json { "success": true, "message": "ok", "data": [ { "account_id": "550e8400-e29b-41d4-a716-446655440000", "account_number": "1234567890", "account_name": "PT Merchant Contoh", "account_min_amount": 10000, "account_max_amount": 10000000, "account_active": true, "account_quota": 30, "channel_id": "660e8400-e29b-41d4-a716-446655440001", "channel_type": "TRANSFER", "channel_name": "Bank Central Asia", "app_id": "990e8400-e29b-41d4-a716-446655440002", "app_name": "BCA Mobile", "app_package": "com.bca" }, { "account_id": "550e8400-e29b-41d4-a716-446655440003", "account_number": "QRIS-001", "account_name": "PT Merchant Contoh", "account_min_amount": 1000, "account_max_amount": 5000000, "account_active": true, "account_quota": 30, "channel_id": "660e8400-e29b-41d4-a716-446655440004", "channel_type": "QRIS", "channel_name": "QRIS", "app_id": null, "app_name": null, "app_package": null } ] } ``` -------------------------------- ### Bad Request Response - Missing Channel ID Source: https://docs.bayar.digital/channel-instructions This example shows a 400 Bad Request response when the required 'channel_id' parameter is missing from the URL. ```json { "success": false, "code": "channel_id is required", "message": "channel id is required" } ``` -------------------------------- ### Verify Webhook Signature in Python Source: https://docs.bayar.digital/webhook Example function to verify the webhook signature using Python's hmac and hashlib modules. The JSON body must be serialized with separators to match the expected format. ```python import hmac, hashlib, json def verify_webhook(body, signature, secret): expected = hmac.new( secret.encode(), json.dumps(body, separators=(',', ':')).encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature) ``` -------------------------------- ### Verify Webhook Signature in PHP Source: https://docs.bayar.digital/webhook Example function to verify the webhook signature using PHP's hash_hmac and hash_equals functions. Use json_encode with default options for body serialization. ```php function verifyWebhook($body, $signature, $secret) { $expected = hash_hmac('sha256', json_encode($body), $secret); return hash_equals($expected, $signature); } ``` -------------------------------- ### Successful Payment Mutations Response Source: https://docs.bayar.digital/payment-mutations This is an example of a successful response when retrieving payment mutations. It includes details about the transaction, sender, amount, and matching status. ```JSON { "success": true, "message": "ok", "data": [ { "id": "550e8400-e29b-41d4-a716-446655440020", "device_id": "660e8400-e29b-41d4-a716-446655440030", "title": "Transfer dari BUDI SANTOSO", "body": "BCA ke 1234567890 a/n PT Merchant Contoh", "posted_at": "2026-06-11T10:30:00Z", "amount_parsed": 50123, "sender_parsed": "BUDI SANTOSO", "type_parsed": "CREDIT", "currency_parsed": "IDR", "is_matched": false, "matched_payment_id": null, "matched_payment_code": null, "created_at": "2026-06-11T10:30:05Z", "device_name": "HP Kantor", "package_name": "com.bca", "app_name": "BCA Mobile", "app_version": "6.2.0", "account_number": "1234567890", "account_name": "PT Merchant Contoh", "bank_name": "Bank Central Asia", "bank_type": "TRANSFER" } ], "pagination": { "total": 50, "count": 1, "per_page": 20, "current_page": 1, "total_pages": 3 } } ``` -------------------------------- ### Get Payment Detail Source: https://docs.bayar.digital/payment-detail Retrieves specific details of an invoice based on the provided payment code. This endpoint is useful for checking the status of an invoice at any time. ```APIDOC ## GET /gateway/payments/{payment_code} ### Description Retrieves specific details of an invoice based on the provided payment code. ### Method GET ### Endpoint `https://api.bayar.digital/gateway/payments/{payment_code}` ### Parameters #### Path Parameters - **payment_code** (string) - Required - The invoice code from your system. #### Header Parameters - **X-Api-Key** (string) - Required - Your merchant API Key. ### Request Example ```curl https://api.bayar.digital/gateway/payments/INV-2026-0001 \ -H "X-Api-Key: pk_..." ``` ### Response #### Success Response (200 OK) - **payment_id** (uuid) - Unique invoice ID. - **payment_code** (string) - The invoice code from your system. - **payment_amount** (int64) - Original amount (without unique code). - **payment_unique** (int64) - Unique payment code (1-999). - **payment_total** (int64) - Total amount to be paid = `amount` + `unique`. - **payment_status** (string) - `PENDING` / `PAID` / `EXPIRED` / `CANCELLED`. - **payment_expired_at** (datetime) - Payment deadline. - **payment_updated_at** (datetime) - Last update time. - **payment_webhook_url** (string/null) - Webhook URL specific to this invoice. - **payment_checkout_url** (string) - Public checkout page URL. - **payment_return_url** (string/null) - URL to redirect the customer. - **customer_name** (string/null) - Customer name. - **customer_email** (string/null) - Customer email. - **customer_phone** (string/null) - Customer phone number. - **customer_orders** (array/null) - List of customer order items. - **account_id** (uuid) - Recipient account ID. - **account_number** (string/null) - Bank account number / QRIS URL. - **account_name** (string/null) - Account holder name. - **channel_id** (uuid/null) - Payment channel ID. - **channel_name** (string/null) - Bank name / payment method. - **channel_type** (string/null) - `TRANSFER` / `QRIS`. - **channel_instructions** (array) - Payment instructions or steps. - **is_manual_match** (bool) - Indicator if the transaction was manually matched. - **manual_matched_mutation_id** (uuid/null) - Related mutation ID (if manually matched). #### Response Example (200 OK) ```json { "success": true, "message": "ok", "data": { "payment_id": "660e8400-e29b-41d4-a716-446655440010", "payment_code": "INV-2026-0001", "payment_amount": 50000, "payment_unique": 123, "payment_total": 50123, "payment_status": "PAID", "payment_expired_at": "2026-10-11T12:00:00Z", "payment_updated_at": "2026-06-11T10:05:00Z", "payment_webhook_url": "https://yourserver.com/webhooks/bayar", "payment_checkout_url": "https://bayar.digital/checkout/660e8400-e29b-41d4-a716-446655440010", "payment_return_url": "https://yourserver.com/orders/INV-2026-0001", "customer_name": "Budi Santoso", "customer_email": "budi@example.com", "customer_phone": "081234567890", "customer_orders": [ { "name": "Produk A", "price": 50000, "quantity": 1, "subtotal": 50000 } ], "account_id": "550e8400-e29b-41d4-a716-446655440000", "account_number": "1234567890", "account_name": "PT Merchant Contoh", "channel_id": "660e8400-e29b-41d4-a716-446655440001", "channel_name": "Bank Central Asia", "channel_type": "TRANSFER", "channel_instructions": [], "is_manual_match": false, "manual_matched_mutation_id": null } } ``` #### Error Responses - **403 Forbidden** - **Code**: `tenant_api_key_required` - **Message**: API Key is invalid or not included. - **Response Example**: ```json { "success": false, "code": "tenant_api_key_required", "message": "API Key is invalid or not included." } ``` - **404 Not Found** - **Code**: `not_found` - **Message**: `payment_code` not found in the system. - **Response Example**: ```json { "success": false, "code": "not_found", "message": "payment not found" } ``` - **500 Internal Server Error** - **Message**: An issue occurred on the server, please try again in a moment. - **Response Example**: ```json { "success": false, "code": "internal_error", "message": "An issue occurred on the server, please try again in a moment." } ``` ``` -------------------------------- ### Get Payment Mutations with Unmatched Filter Source: https://docs.bayar.digital/payment-mutations Use this endpoint to retrieve a list of incoming transactions. The `only_unmatched=true` parameter filters the results to show only transactions that have not been automatically matched with an invoice. ```cURL curl "https://api.bayar.digital/gateway/mutations?only_unmatched=true" \ -H "X-Api-Key: pk_..." ``` -------------------------------- ### Webhook Payload Structure Source: https://docs.bayar.digital/webhook This section describes the JSON payload structure that Bayar Digital sends to your server for payment notifications. It includes details on each field and examples for different payment statuses. ```APIDOC ## Webhook Payload Structure ### Description This section describes the JSON payload structure that Bayar Digital sends to your server for payment notifications. It includes details on each field and examples for different payment statuses. ### Payload Fields | Field | Type | Description | |--------------|--------|-----------------------------------------------------------| | `payment_id` | uuid | Unique invoice ID in the Bayar Digital system | | `payment_code`| string | Internal invoice code from your system | | `status` | string | Current payment status: `PAID` / `EXPIRED` / `CANCELLED` | | `amount` | int64 | **Final total** to be paid (original nominal + unique code) | | `paid_at` | datetime| Time of successful payment (ISO 8601). Only appears if status is `PAID`. | ### Examples Based on Status **PAID:** ```json { "payment_id": "660e8400-e29b-41d4-a716-446655440010", "payment_code": "INV-2026-0001", "status": "PAID", "amount": 50123, "paid_at": "2026-06-11T10:05:00Z" } ``` **EXPIRED:** ```json { "payment_id": "660e8400-e29b-41d4-a716-446655440010", "payment_code": "INV-2026-0001", "status": "EXPIRED", "amount": 50123 } ``` **CANCELLED:** ```json { "payment_id": "660e8400-e29b-41d4-a716-446655440010", "payment_code": "INV-2026-0001", "status": "CANCELLED", "amount": 50123 } ``` ```