Deposit (Payin) — V2
AUTOPayin V2 (AUTO) creates an incoming payment without forcing you to choose a channel up front. The gateway selects QR or bank transfer automatically by provider weight at request time — so you get the best available route with a single call.
What's new vs V1
- receiving_type is not a request field — by default the gateway auto-selects the channel (AUTO). Optionally send channel (bank, qrcode, or crypto) to force a specific rail.
- New payment_theme field — themes the hosted Pay Page (e.g. "aurora"); blank or unknown falls back to "classic".
- The gateway may route to QR or bank transfer automatically; the channel it picked is returned as receiving_type in the webhook callback.
Typical steps:
- Create a payin with POST /v2/payins (omit channel and the gateway auto-selects; or send channel to force bank/qrcode/crypto)
- Open the returned paypage_url (hosted Pay Page) or send the link to the payer to complete the payment
- Wait for the webhook callback when the payin reaches a final status; GET /v1/payins/{id} is available if you want to check on demand
- To attach a slip, do it through the Pay Page first; if that runs into trouble, fall back to POST /v1/payins/{id}/slip (multipart field "slip": jpg/jpeg/png/jfif, max 10MB; up to 10 uploads per payin shared across both channels)
Create a payin (AUTO)
By default you omit channel and the gateway auto-selects the rail (QR or bank transfer). Optionally send channel — bank, qrcode, or crypto — to force a specific rail; crypto also takes a network. There is still no receiving_type in the request. Add the optional payment_theme to control the Pay Page look.
POST /v2/payins
Content-Type: application/json
{
"merchant_ref_id": "unique-order-ref-id-2025001",
"customer_id": "0198cbc6-fd02-7459-a671-78c306d0a1ad",
"customer_account_id": "0198cbc7-35ec-7c8c-8994-d3ac3e7f5255",
"currency_code": "THB",
"request_amount": 1000,
"payment_theme": "aurora",
"notes": "Payin for invoice #INV2025001",
"redirect_url": "https://merchant.example.com/payment/success"
}Request fields
| Field | Type | Required | Description |
|---|---|---|---|
merchant_ref_id | string · max 255 | Required | Your own reference for this order/invoice. Must be unique within your organization and is never freed — even after the payin fails, expires, or is cancelled. Sending an already-used value is rejected with 409 RESOURCE_EXISTS; when a retry hits this, look the original up with GET /v1/payins?merchant_ref_id=... instead of creating a new reference. |
customer_id | string · UUID | Required | UUID of an existing customer (create one first via the Customers API). |
customer_account_id | string · UUID | Required | UUID of the customer account that will receive the payment (belongs to customer_id). |
currency_code | string | Required | ISO 4217 currency code, e.g. THB. |
request_amount | number · integer, aligned to currency unit_step | Required | Amount to receive as integer minor units aligned to the currency unit_step. Per-transaction min/max depend on your organization: check GET /v1/me/limits — min_deposit / max_deposit are the effective limits (configured_* are the raw organization-level bounds). |
payment_theme | string · max 32 | Optional | Theme key for the hosted Pay Page, e.g. "aurora". Blank or unknown falls back to "classic". — Pay Page |
notes | string · max 255 | Optional | Free-text note attached to the payin. |
redirect_url | string · max 2048 | Optional | HTTPS URL (max 2048 chars) the Pay Page auto-redirects the payer to (5-second countdown) when the payin ends as success or expired — other outcomes do not redirect, and no query params are appended (no id/status), so embed your own reference in the URL. |
channel | string · enum | Optional | Force the receiving rail: bank (the default when omitted — the gateway auto-selects), qrcode, or crypto. Omit it and the gateway auto-selects QR/bank transfer (AUTO). |
network | string · enum | Optional | Crypto network when channel = crypto (for example TRC20); omit to use the provider default network. Ignored for other channels. |
Response
On success the API returns 201 Created with the payin wrapped in the standard SuccessResponse envelope. status is "pending". Unlike V1, V2 does not return any receiving_* payment details — open the returned paypage_url to let the payer complete the payment (hosted Pay Page); the picked channel arrives later as receiving_type in the webhook callback. transfer_amount is the exact amount the payer must send (request_amount plus at most 0.99, per allocation); the payin auto-expires at expired_at if unpaid. fee_amount / net_amount at creation time are estimates — the final values arrive with the callback.
Success (201 Created)
HTTP/1.1 201 Created
Content-Type: application/json
{
"success": true,
"data": {
"id": "0198cbff-db9b-7b6a-9f82-6a55fdfb6575",
"merchant_ref_id": "unique-order-ref-id-2025001",
"currency_code": "THB",
"customer_id": "0198cbc6-fd02-7459-a671-78c306d0a1ad",
"customer_name": "John Doe",
"customer_account_id": "0198cbc7-35ec-7c8c-8994-d3ac3e7f5255",
"customer_account_holder_name": "J. Doe",
"notes": "Payin for invoice #INV2025001",
"redirect_url": "https://merchant.example.com/payment/success",
"request_amount": 1000,
"transfer_amount": 1000.01,
"paid_amount": 0,
"net_amount": 995.00,
"fee_amount": 5.00,
"status": "pending",
"payment_theme": "aurora",
"paypage_url": "https://pay.example.com/pay/0198cbff-db9b-7b6a-9f82-6a55fdfb6575",
"finalized_at": null,
"expired_at": 1755769966,
"created_at": 1755769366,
"updated_at": 1755769366
}
}| Field | Description |
|---|---|
id | Payin id — use it to look up the payin via GET /v1/payins/{id} when needed |
status | Always "pending" right after creation |
paypage_url | Hosted Pay Page URL — send the payer here to complete payment |
transfer_amount | Exact amount the payer must transfer — request_amount plus a fraction of at most 0.99 added by allocation (the extra is a fee charged to the customer) |
expired_at | Unix seconds when the payin expires if left unpaid — set to roughly 10 minutes after the payin enters processing (system-wide default; not configurable per request or per organization) |
Error (4xx / 5xx)
On failure the API returns a non-2xx status with the standard envelope: success is false, data is null, error is human prose, and code is the machine-readable identifier your client should branch on. Use the X-Request-Id response header when contacting support. Example below: 400 Bad Request for an invalid amount.
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"success": false,
"data": null,
"error": "amount must be greater than zero",
"code": "AMOUNT_INVALID",
"meta": {
"request_id": "019f2d24-1a02-7b31-9c44-2f61c0d8a9e1",
"timestamp": 1783168800
}
}How AUTO channel selection works
When you omit channel, G2Wallet evaluates the available providers at request time and assigns the channel automatically (QR / bank transfer). Send an explicit channel (bank/qrcode/crypto) to force a rail instead. Either way the resolved channel is returned as receiving_type in the webhook callback, so your reconciliation logic stays the same.
Webhook callback
Identical to V1. When a payin reaches a terminal status (success / failed / refunded / expired), G2Wallet POSTs the full transaction body to your configured webhook URL. Verify the signature, then update your internal state.
How to verify the callback X-Signature is covered in Authentication → Verifying webhook callbacks.
Request headers
Every callback is delivered with the headers below (example values). Use X-Signature and X-Timestamp to verify authenticity, X-Transaction-ID to match the transaction (same as id in the body), and X-Request-ID to de-duplicate retried deliveries.
POST /your-webhook-endpoint HTTP/1.1
Content-Type: application/json
Accept: application/json, text/plain
User-Agent: P2PPAY-Callback/1.0
X-Callback-Version: 1.0
X-Timestamp: 1737029438
X-Signature: 3f7a1c9e5b2d8f04a6c1e7b9d3a5f2c8e0b4d6a8c2f4e6b8d0a2c4e6f8a0b2c4
X-Request-ID: cb_1737029438123456789_123456
X-Transaction-ID: 019db53f-b389-7149-8806-4e64f3be45a8
X-Organization-ID: 0198cbc5-8a3d-7e21-b4f6-9c2d1e0f3a5b
X-Transaction-Type: payinRequest body
{
"id": "019db53f-b389-7149-8806-4e64f3be45a8",
"transaction_type": "payin",
"status": "success",
"merchant_ref_id": "ORDER-2026-00081",
"currency_code": "THB",
"request_amount": 1000,
"paid_amount": 1000,
"transfer_amount": 1000,
"fee_amount": 25,
"net_amount": 975,
"receiving_type": "BANK_TRANSFER",
"receiving_platform": "KBANK",
"receiving_identifier": "0800000000",
"receiving_holder_name": "Sample S.",
"payment_theme": "aurora",
"customer_id": "0198cbc6-fd02-7459-a671-78c306d0a1ad",
"customer_account_id": "0198cbc7-35ec-7c8c-8994-d3ac3e7f5255",
"customer_name": "John Doe",
"customer_account_holder_name": "John Doe",
"created_at": 1737029400,
"expired_at": 1737030000,
"updated_at": 1737029438,
"finalized_at": 1737029438,
"notes": null
}Key fields to use
| Field | Description |
|---|---|
id | Payin id — use it to query /v1/payins/{id} |
status | pending | processing | success | failed | rejected | refunded | expired | cancelled (lowercase) — only "success" means funds received; see the Payin statuses table below |
merchant_ref_id | Your reference id — match to your order/invoice |
receiving_type | The channel the gateway actually selected (QR / BANK_TRANSFER ...) |
payment_theme | The Pay Page theme that was applied, if you sent one |
paid_amount | Actual amount the payer sent |
transfer_amount | The actual amount the customer needs to transfer — equal to request_amount or with at most 0.99 added by allocation. The extra is a fee charged directly to the customer (separate from the Merchant fee) |
fee_amount | Merchant fee per the fee setting of your organization, calculated from request_amount — deducted on success before the wallet credit (net_amount = paid − fee) |
net_amount | Amount credited to your wallet (paid − fee) |
finalized_at | Unix seconds when the transaction was settled |
transaction_type | Always "payin" for this event |
Payin statuses
Every status a payin can be in — across the create response, GET /v1/payins/{id}, list filters, and webhook callbacks. Only "success" means funds were received.
| Status | Meaning | Final? | Callback? |
|---|---|---|---|
pending | The payin is being created — data is still being validated. A short-lived state right after POST. | No — moves on to processing | — |
processing | Created successfully and ready for the customer to pay. Covers the whole active window: waiting for the transfer, and verification after the customer pays or uploads a slip. | No | — |
success | Funds received — credit your customer now. | Almost — can still flip to rejected → refunded if the payment is later refunded | ✓ |
failed | The payin failed. | Yes | ✓ |
rejected | Awaiting refund — an in-between state on the way to refunded. | No — becomes refunded | ✓ |
refunded | The money was returned to the payer. | Yes | ✓ |
expired | Reached expired_at without a confirmed payment. | Almost — can still flip to success if a verification already in progress completes later | ✓ |
cancelled | Cancelled via POST /v2/payins/{id}/cancel. | Yes | ✕ — no callback; the cancel API response tells you the result |
Full request/response details are in the API Reference → Payins