Part 3–5
Payin
Create payins, read QRIS/VA instructions, fetch the QR image, list payins, and handle statuses.
Create a payin
/api/v1/payinspayins:writeResponse 202 AcceptedThe API accepts the request to create an instruction; this response does not yet prove that the customer has paid.
| Field | Required | Description |
|---|---|---|
reference | Yes | Unique merchant order number. |
amount / currency | Yes | Amount in rupiah and IDR. |
channel | Yes | QRIS_GOPAY, QRIS_QOIN, VA_QOIN, or BANK_TRANSFER. |
bank_code | Yes* | Required for VA_QOIN and BANK_TRANSFER; take it from GET /channels. Not sent for QRIS. |
customer_reference | No | Customer ID in the merchant's system, at most 64 characters. |
metadata | No | Extra information within the limits in Data formats. |
POST /api/v1/payins
Authorization: Bearer <api_key>
Idempotency-Key: order-0001-create
Content-Type: application/json
{
"reference": "ORDER-0001",
"amount": "100000",
"currency": "IDR",
"channel": "QRIS_GOPAY",
"customer_reference": "CUST-100",
"metadata": { "order_id": "ORDER-0001" }
}{
"data": {
"id": "pin_example_001",
"reference": "ORDER-0001",
"status": "QUEUED",
"status_url": "/api/v1/payins/pin_example_001",
"created_at": "2026-10-15T05:00:00Z"
},
"request_id": "req_example_001"
}id and reference, then poll the GET details until the instruction is available. Do not request a new QR repeatedly while the status is QUEUED/CREATING. To switch payment method, create a new order/payin only after the payment risk of the old instruction has been handled.Unknown fields are rejected. The expiry is set by the channel/provider and returned in the details. Clients must not assume a QR or VA always lasts the same duration. An inactive channel or an amount outside the limits is rejected before the intent is accepted.
Payin details
/api/v1/payins/{id}payins:readResponse 200payment_instruction is null while the instruction is not yet available. Fee and net on an unpaid transaction are estimates based on the policy at creation time.
{
"data": {
"id": "pin_example_001",
"reference": "ORDER-0001",
"status": "AWAITING_PAYMENT",
"channel": "QRIS_GOPAY",
"amount": "100000",
"fee": "1500",
"net": "98500",
"currency": "IDR",
"settlement_status": "NOT_ELIGIBLE",
"resource_version": 2,
"payment_instruction": {
"type": "QR",
"image_path": "/api/v1/payins/pin_example_001/qr",
"expires_at": "2026-10-15T05:15:00Z"
},
"created_at": "2026-10-15T05:00:00Z",
"updated_at": "2026-10-15T05:00:20Z",
"paid_at": null
},
"request_id": "req_example_002"
}payment_instruction shapes for VA and bank
{
"type": "VA",
"bank_code": "BANK_CODE",
"account_number": "880012345678",
"account_name": "NAMA PEMBAYARAN",
"amount": "100000",
"currency": "IDR",
"expires_at": "2026-10-15T06:00:00Z"
}{
"type": "BANK_TRANSFER",
"bank_code": "BANK_CODE",
"account_number": "1234567890",
"account_name": "NAMA REKENING ANTARAPAY",
"amount": "100000",
"currency": "IDR",
"payment_reference": null,
"expires_at": "2026-10-15T06:00:00Z"
}bank_code, account numbers, names, IDs, and times are illustrative. Display the values returned by the API without replacing the account or adding your own unique code. If payment_reference is present, pass its instructions on to the customer. Do not assume that a proof of payment uploaded by the customer means the payment succeeded.Fee proposal used in the example: 1.5% on a successful payin, deducted from the merchant's entitlement. Gross Rp100,000 → fee Rp1,500 → net Rp98,500. Use the fee/net values in the response as the integration reference; confirm the effective rate and who bears it at activation.
Fetch the QR image
/api/v1/payins/{id}/qrpayins:readResponse 200 image/pngA successful response is a binary PNG with Content-Type: image/png and Cache-Control: private, no-store; on success this endpoint does not return a JSON envelope.
curl "$ANTARAPAY_BASE_URL/api/v1/payins/pin_example_001/qr" \
-H "Authorization: Bearer $ANTARAPAY_API_KEY" \
-H "Accept: image/png" --output qris.pngYour merchant backend fetches the image and serves it through your own endpoint, which authorizes the customer's order. The API key is never sent to the browser. Errors are still JSON:
| HTTP | Code | Meaning |
|---|---|---|
| 409 | QR_NOT_READY | The instruction is not ready; keep polling the details. |
| 409 | INVALID_STATE | The transaction is no longer awaiting payment. |
| 410 | QR_EXPIRED | The QR has expired. |
| 422 | CHANNEL_NOT_QR | The payin channel is not a QRIS channel. |
List payins
/api/v1/payins?reference=ORDER-0001&limit=50payins:readOptional filters: reference, channel, status, from, to, cursor, limit. Date filters use created_at. Reference search is an exact match. The list contains summaries without payment_instruction; fetch the details for the instruction.
{
"data": [
{
"id": "pin_example_001",
"reference": "ORDER-0001",
"status": "SUCCEEDED",
"channel": "QRIS_GOPAY",
"amount": "100000",
"fee": "1500",
"net": "98500",
"currency": "IDR",
"settlement_status": "COMPLETED",
"created_at": "2026-10-15T05:00:00Z"
}
],
"pagination": { "next_cursor": null, "has_more": false },
"request_id": "req_example_003"
}Payin statuses
| Status | Client action |
|---|---|
QUEUED / CREATING | Show that the payment is being prepared; wait or poll. |
AWAITING_PAYMENT | Show the instruction and expiry; wait for server confirmation. |
SUCCEEDED | Payment verified; process the order idempotently. |
EXPIRED / FAILED | Stop showing the old instruction; read the error/details if present. |
NEEDS_REVIEW | Show that it is being reviewed; do not treat it as successful or blindly retry. |
Succeeded does not mean the balance is available
SUCCEEDED maps to completed in the webhook, but the funds do not necessarily reach the Active Balance: settlement_status may be WAITING_FUNDS. An EXPIRED payin can become SUCCEEDED after a late payment is verified; process the completed webhook with the newer resource_version.Suggested polling: every 2 seconds with backoff up to 30 seconds; respect the rate limit. Queue and review statuses are not sent as final webhooks.