Skip to content
Appearance
Get an API key

Turn your ABA payment link into a payment API.

One POST returns a KHQR, a deeplink, and an expiry. We watch the transaction and sign you a webhook when it settles.

Create a payment
POST /payment
Authorization: Bearer sk_live_…
Content-Type: application/json

{
  "storeId": "str_8f21c4",
  "amount": "12.50",
  "currency": "USD",
  "orderId": "A-1042"
}
KHQR

Tutoat Demo Store

12.50USD

$
Awaiting payment3:00 remaining

An ABA payment link is an interface for a human, not for your software.

Today the link is the whole integration surface. You send it, the customer types the amount themselves, and your system finds out it was paid when somebody tells it. There is no call to make, no identifier to correlate, and no event to subscribe to.

What the link gives you

  • An amount typed by the payer, so wrong often enough.
  • No id to reconcile against your own order.
  • No callback, no status endpoint, nothing to poll.
  • Confirmation by a person reading a banking app.
  • No history you can query after the fact.

What the API gives you

  • An amount your backend sets, exact by construction.
  • A payment id, and your own orderId carried alongside it.
  • A signed webhook, plus a record you can read back.
  • A terminal state your code can branch on.
  • Permanent, paged, filterable history.

The link already works. It just had no API. We gave it one.

Create a payment. Render the QR. Handle one webhook.

Four steps, and only the first two are yours to write. This is the whole mechanism — there is nothing else behind it.

  1. 01

    Register the link you already have

    Once

    Add a store, paste its ABA PayWay link, create an API key. No merchant onboarding, no sandbox ceremony, no approval to wait through.

  2. 02

    POST an amount

    Per order

    One authenticated request carrying the store and the amount, plus your own order id if you have one. It returns a payment id, a KHQR payload, an ABA Mobile deeplink, and an expiry timestamp.

  3. 03

    Render the QR wherever you like

    Your surface

    The payload is a plain KHQR string. Draw it on a web checkout, a POS screen, a printed slip, or a chat message — any KHQR-capable banking app can pay it.

  4. 04

    Take the webhook, or poll

    Automatic

    Tutoat checks with the bank every three seconds until the payment settles or the QR expires, writes the outcome to a permanent record, and posts a signed webhook to your server.

Your request
POST /payment
Authorization: Bearer sk_live_…
Content-Type: application/json

{
  "storeId": "str_8f21c4",
  "amount": "12.50",
  "currency": "USD",
  "orderId": "A-1042"
}
What comes back
{
  "id": "pay_3kd91x",
  "status": "PENDING",
  "qrString": "00020101021229400015kh.gov.nbc…",
  "deeplink": {
    "scheme": "…",
    "android": "…"
  },
  "expireAt": "2026-08-14T09:32:11Z"
}

The checkout you build, confirming itself.

Render the qrString and the amount, then show the status you already have. It moves to Success without a refresh, because Tutoat is polling the bank every three seconds behind it and your page is reading the record.

On a phone, the deeplink in the same payload hands the customer straight to ABA Mobile with the amount already filled in. The QR always stays on screen as the fallback.

Tutoat Demo Store3:00
KHQR

Tutoat Demo Store

12.50USD

$
PendingChecking every 3s

Demo payment is pending.

REST, API keys, webhooks. Nothing you have not used before.

Every endpoint takes a server-side key as a bearer token. Keys are prefixed sk_live_, shown once at creation, rate limited to 600 requests a minute, expire after 90 days, and are scoped to what they may do.

MethodPathWhat it does
POST/paymentCreate a payment. Returns the KHQR, the deeplink, and the expiry.
GET/payment?storeId=…&page=1List and page a store's payments.
GET/storesList the stores on the account.
Webhook we post to you
{
  "id": "pay_3kd91x",
  "storeId": "str_8f21c4",
  "status": "SUCCESS",
  "amount": "12.50",
  "currency": "USD",
  "orderId": "A-1042"
}

Verifying it came from us

Each webhook is signed with HMAC-SHA256 over the raw request body, using the signing secret you set on the store. Recompute it on your side and compare before you trust the message.

If your server does not answer, delivery is retried ten times across roughly seventeen minutes. The payment record stays authoritative, so you can read a settled payment back instead of depending on the callback.

Everything around the payment, not just the payment.

KHQR from a single call

A store id and an amount return a KHQR payload ready to render anywhere. Amounts take two decimal places and the currency follows whatever the store's PayWay link is set to.

A deeplink in the same payload

Every payment also carries an ABA Mobile deeplink, which is what makes mobile checkout work when there is no second device to scan with. Android falls through to the Play Store if the app is missing; the QR stays on screen as the fallback either way.

Confirmation you do not have to build

The service polls the bank every three seconds from issue to outcome. You get the result without writing a reconciliation job or asking anyone to open a banking app.

Idempotency keys

Send an idempotency key and a repeated request returns the original payment instead of issuing a second QR. Retrying a timed-out request stops being a billing problem.

Stores under one account

One account holds many stores, each with its own PayWay link, name, and on/off switch. Payments and history are scoped per store; an account's API keys reach every store it owns.

A queryable payment record

Every payment is kept permanently — amount, currency, status, your order id, the settled transaction id, the receipt link, and timestamps. Paged, and filterable by status or by your own order id.

Built to stay up under load

Requests to the bank are rate-budgeted, spread across outbound routes, and cut off automatically when the bank is unhealthy, so one bad minute upstream does not cascade into failed checkouts.

The money goes straight to the merchant’s bank. We only tell you it arrived.

The money never touches us

Funds move from the payer straight into the merchant's own ABA account. Tutoat holds no balance, runs no payout schedule, and carries no float.

No card data in the system

The service handles QR payments only. There are no card numbers collected, transmitted, or stored anywhere, so there is nothing sensitive to leak.

Reads are scoped to the account

A payment or store belonging to someone else is indistinguishable from one that does not exist, so ids cannot be enumerated or probed from another account's key.

Webhooks you can verify

Each callback is signed with HMAC-SHA256, so your server can prove a settlement message came from us and was not forged by anyone who guessed your endpoint.

The questions that come up in review.

Do I need a merchant account with the bank?

No. Tutoat works against the ABA PayWay link the merchant already has. There is no integration project, no paperwork, and no approval process standing between you and your first payment.

What happens if my server misses a webhook?

Delivery is retried ten times across roughly seventeen minutes. Beyond that, the payment record is authoritative and permanent — you can read the settled status back at any time instead of relying on the callback.

What happens if the QR expires?

Nothing is charged. The bank sets the window, typically around three minutes. Expiry is a normal terminal outcome rather than an error, so handle it as a state and create a new payment.

Expired
How do API keys scope to stores?

Keys belong to the account, not to an individual store, so one key reaches every store that account owns. Keys are prefixed sk_live_, shown once at creation, rate limited to 600 requests a minute, and expire after 90 days.

Which banking apps can pay it?

Any KHQR-capable banking app can scan the code. On a phone, the deeplink in the payment payload can hand the customer straight into ABA Mobile with the amount already filled in.

Your first payment, in about ten minutes.

Add the store, paste the PayWay link, create a key, and POST your first amount.