Migration from UTpay
This guide helps you move from the Legacy (UTpay) API to the Merchant API V1. The V1 API offers a simpler auth model, richer response objects, and consistent minor-unit amounts.
You do not have to migrate. The Legacy API remains fully supported indefinitely. Migrate only when it is convenient for your development cycle.
Side-by-Side Comparison
Authentication
| Legacy API | Merchant API V1 | |
|---|---|---|
| Orders | SHA-512 body signature | X-API-Key header |
| Disbursements | RSA X-SIGNATURE header | X-API-Key header |
| Credentials | legacy_client_id + legacy_client_secret + RSA key | api_key only |
V1 change: Remove all signature computation. Add X-API-Key header to every request.
Amount Format
| Legacy API | Merchant API V1 | |
|---|---|---|
| Create order | totalAmount: 50000 (IDR 50,000) | amount_minor: 5000000 (IDR 50,000) |
| Disbursement request | requestAmount: 500000 (IDR 500,000) | amount_minor: 50000000 |
| Balance | "5000000.00" (decimal string) | 5000000000 (integer minor units) |
V1 change: Multiply all IDR amounts by 100 on the way in. Divide by 100 on the way out.
URL Path Changes
| Legacy path | V1 path | Method |
|---|---|---|
POST /api/order/create | POST /v1/orders | Create order |
POST /api/order/query | GET /v1/orders/:ref_code | Get order |
POST /api/disbursement/get-balance | GET /v1/balance | Wallet balance |
POST /api/disbursement/create | POST /v1/disbursements (channel: BANK) | Bank disbursement |
POST /api/disbursement/create-ewallet | POST /v1/disbursements (channel: EWALLET) | E-wallet disbursement |
POST /api/disbursement/query | GET /v1/disbursements/:ref_code | Get disbursement |
Request Field Mapping
Create Order
| Legacy field | V1 field | Notes |
|---|---|---|
clientId | (removed) | Identified by API key |
orderId | merchant_order_id | Same purpose |
totalAmount | amount_minor | × 100 |
paymentType | payment_method | Same values (QRIS, BANK_TRANSFER) |
signature | (removed) | Auth via header |
customerName | customer_name | snake_case |
customerEmail | customer_email | snake_case |
customerPhone | customer_phone | snake_case |
notifyUrl | notify_url | snake_case |
returnUrl | return_url | snake_case |
Create Bank Disbursement
| Legacy field | V1 field | Notes |
|---|---|---|
clientId | (removed) | Auth via header |
orderId | idempotency_key | Same purpose |
bankCode | bank_short_code | Same values |
accountNumber | account_number | snake_case |
accountName | account_name | snake_case |
requestAmount | amount_minor | × 100 |
channel | channel: "BANK" | Add explicitly |
Response Shape
Order
| Legacy field | V1 field |
|---|---|
data.refCode | data.ref_code |
data.orderId | data.merchant_order_id |
data.totalAmount | data.amount_minor (÷ 100 to convert) |
data.transactionTime | data.created_at (ISO 8601, UTC) |
data.paidAt | data.paid_at (ISO 8601, UTC) |
data.qrString | data.qris_string |
Webhook
| Legacy field | V1 field |
|---|---|
refCode | ref_code |
orderId | merchant_order_id |
totalAmount (IDR int) | amount_minor (minor units) |
status | status + event |
paidAt (WIB string) | paid_at (ISO 8601 UTC) |
Migration Checklist
Use this checklist to migrate without downtime:
- Obtain your V1
api_keyfrom the merchant dashboard - Run both integrations in parallel: Legacy for existing orders, V1 for new orders
- Update create-order code: use
X-API-Key, rename fields, multiply amounts × 100 - Update webhook handler: new field names, divide
amount_minor÷ 100 for display - Update disbursement code: use
X-API-Key, rename fields, update channel field - Update balance display: V1 returns
balance_minor(integer), ÷ 100 for IDR - Test full flow: create order → pay → receive webhook → query status
- Test disbursements: get balance → create → query
- Switch all new traffic to V1
- Monitor for errors for 1 week
- Notify your account manager to disable Legacy credentials (optional)
Frequently Asked Questions
Will my orderId (Legacy) conflict with merchant_order_id (V1)?
No. These are stored separately. You can have the same string as both a legacy orderId and a V1 merchant_order_id for different orders.
Can I use the Legacy API for some orders and V1 for others?
Yes. Both APIs are fully active simultaneously. The source_api field on orders in the Admin Dashboard indicates which API was used.
What happens to my RSA key pair after migration?
Nothing — your keys remain stored. You only need them as long as you use the Legacy API.
Do settlement calculations change when I migrate?
No. MDR, fees, and net payout calculations are identical regardless of which API created the order.