PAYWISE

API reference

PAYWISE Public API — v1

Base URL: https://www.paywise.asia/api/v1

This page describes what's actually implemented today. Payment methods are QRIS and USDT only. There is currently no live payment provider connected — transactions you create are recorded as pending and stay that way until a provider integration exists to move them to paid or failed.

Authentication

Every request — except the provider webhook endpoint — requires an API key, generated from your dashboard under API Keys.

Authorization: Bearer pw_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The full key is shown once, at creation time, and never again — PAYWISE stores only a hash of it. Rate limit is 60 requests/minute per API key; exceeding it returns 429 RATE_LIMITED. Every response — success or error — carries an X-Request-Id header, also visible against the matching row in your API Logs dashboard.

Environments

Every API key belongs to exactly one environment, visible directly in its prefix:

Sandbox

pw_test_…

Transactions created with a sandbox key are tagged sandbox and never appear in your live reports or default dashboard view.

Live

pw_live_…

Transactions created with a live key are production transaction data and count toward your live dashboard stats, reports, and settlement records.

Switch which environment your dashboard is viewing with the Live/Sandbox toggle in the sidebar. This is a display preference only — the environment a transaction is written into is always determined by which key authenticated the request, never by anything a client can set directly.

Response envelope

Every response is one of these two shapes — never anything else.

Success

{ "success": true, "data": { ... } }

Error

{ "success": false, "error": { "code": "INVALID_API_KEY", "message": "Invalid API credentials." } }

Error codes

HTTP Code Meaning
401 INVALID_API_KEY Missing, malformed, or unrecognized API key
401 API_KEY_REVOKED The key was revoked from the dashboard
403 MERCHANT_INACTIVE The merchant account is suspended
404 NOT_FOUND Resource doesn't exist, or belongs to another merchant
409 DUPLICATE_REFERENCE Same merchant_reference reused with a different amount/method
422 VALIDATION_ERROR Request body failed validation
429 RATE_LIMITED Too many requests
501 PROVIDER_NOT_CONNECTED Inbound provider webhook — no real provider integration exists

Create a transaction

POST /api/v1/transactions

Field Type Required Notes
merchant_reference string yes Your own order ID. Doubles as the idempotency key.
amount number yes Minimum 0.01
payment_method string yes QRIS (IDR) or USDT (BEP20) only
currency string no Optional. Must match the rail: IDR for QRIS, USDT for USDT. Stored as-is — never converted.
network string no USDT only, required: BEP20. TRC20/ERC20 are rejected. Omit for QRIS.

Request

{
    "merchant_reference": "ORDER-1042",
    "amount": 250000,
    "currency": "IDR",
    "payment_method": "QRIS"
}
{
    "merchant_reference": "ORDER-1043",
    "amount": 100,
    "currency": "USDT",
    "payment_method": "USDT",
    "network": "BEP20"
}

Response — 201 Created

{
    "success": true,
    "data": {
        "reference": "PW20260921143022AB3F",
        "merchant_reference": "ORDER-1042",
        "payment_method": "QRIS",
        "environment": "sandbox",
        "amount": 250000,
        "currency": "IDR",
        "network": null,
        "status": "pending",
        "created_at": "2026-09-21T14:30:22+00:00",
        "paid_at": null
    }
}

Idempotency: retrying with the same merchant_reference and the same amount/method returns the original transaction with 200 OK instead of creating a duplicate. Reusing the reference with a different amount or method returns 409 DUPLICATE_REFERENCE. Sandbox and live keys are separate: the same merchant_reference can be used once in each, and a key never sees the other environment's transactions.

Retrieve a transaction

GET /api/v1/transactions/{reference}

{reference} is the PAYWISE-generated reference returned when you created the transaction — not your own merchant_reference, and never a raw database ID.

You can only ever retrieve your own merchant's transactions. Requesting another merchant's reference returns 404 NOT_FOUND — never a 403, which would confirm the reference exists at all.

Transaction statuses

Pending Paid Failed Expired

The same values are used consistently across the database, this API, the merchant dashboard, and the admin dashboard.

Webhooks

PAYWISE → you

Configure a webhook URL and events from Dashboard → Webhooks. When a transaction's status changes to a subscribed event, PAYWISE sends:

POST <your webhook_url>
Content-Type: application/json
X-PAYWISE-Signature: <hex-encoded HMAC-SHA256>

Verify the signature by computing HMAC-SHA256(raw_json_body, your_webhook_secret) (shown on the Webhooks page) and comparing it to the X-PAYWISE-Signature header. Events: payment.paid · payment.failed · payment.expired.

Delivery only actually fires once a real provider integration exists to move a transaction out of pending. The delivery mechanism itself is real and records actual HTTP response codes, never an assumed success — every attempt is logged on your Webhooks page, including the event, attempt number, actual response status (or nothing, if the request timed out), and up to 2KB of your endpoint's response body.

Provider → PAYWISE

POST /api/v1/webhooks/provider/{provider}

Reserved for QRIS/USDT provider callbacks. Currently returns 501 PROVIDER_NOT_CONNECTED for every request — there is no provider integration wired in yet, so there is no signature to verify, and this endpoint deliberately refuses to trust an unverified payload rather than risk marking a transaction paid incorrectly.