Skip to content

Hosted Checkout

Server-to-server API for hosted checkout. Amounts are integers in minor units (paisa for NPR, cents for USD). amount is tax-inclusive. JSON fields are camelCase. Machine-readable: /openapi.json, /llms.txt.

How it works?

  1. Customer is ready to pay on your site.
  2. Your backend creates an order via POST /api/orders and receives a checkoutUrl.
  3. You redirect the customer to that checkout URL.
  4. They pick a gateway and pay on the hosted page.
  5. They return to your success/failure URL with ?id=&status= (UX only; id is our order UUID).
  6. Your backend calls POST /api/orders/{id}/verify (or handles a signed webhook, then still verifies).
  7. Fulfill only when fulfilled: true.

Getting Started

No SDK required - any backend that can make HTTPS POST requests works. You need a merchant account and an API key from Awecode.

Tip
Request access from the landing page if you do not have credentials yet.

Merchant configuration

Awecode sets these on your merchant (not via this API). API keys and the webhook signing secret are minted on the admin merchant page (key shown once as ps_…; secret is rotatable).

FieldRequiredNotes
Success / failure redirect URLs Fallbacks when an order omits per-order redirects. If every create sends successRedirectUrl / failureRedirectUrl, these are unused for that order - any absolute URL works as placeholder.
Success / failure webhook URLsnopayment_success → success URL; payment_failed → failure URL; receipt_synced → success webhook URL (not the redirect). Same URL for both is fine; branch on event.
Voucher enabled + apply URLno Hosted checkout voucher field. When on, voucherApplyUrl is required. Same HMAC secret as webhooks.
Allow cash tender via APIno Unlocks method: "cash" on create. Off → those calls 403.

API Authorization

Pass your merchant API key as a Bearer token on every request:

Authorization: Bearer ps_...
Content-Type: application/json
StatusMeaning
401Bad / missing / revoked key
403Merchant disabled

API Endpoints

EnvironmentBase URL
Productionhttps://pay.awecode.com
Staginghttps://ps-stg.awecode.workers.dev

Staging API keys and gateway sandbox credentials are issued by Awecode on the staging admin merchant (same form as production).

MethodPathPurpose
POST/api/ordersCreate checkout order
GET/api/orders/{id}Read order status
POST/api/orders/{id}/verifyVerify before fulfillment
POST/api/orders/{id}/cancelCancel open order
POST/api/orders/{id}/cashCash tender on an Open order
POST/api/orders/{id}/returnCash sales return (credit note)
POST/api/orders/{id}/exchangeCN + INV + optional net settle

Create order

Every payment starts as a server-side create. On success you get a checkoutUrl - redirect the customer there.

POST /api/orders

Sample request

{
  "amount": 150000,
  "tax": 0,
  "shipping": 0,
  "currency": "NPR",
  "orderId": "1000123",
  "orderNumber": "1000123",
  "reference": "1000123",
  "message": "Order #1000123",
  "products": [
    {
      "name": "Seto Dharti",
      "quantity": 1,
      "amount": 80000,
      "description": "Ebook"
    }
  ],
  "data": { "yourOrderPk": 12345 },
  "tags": ["vip", "color:red"]
}

JSON payload details

FieldTypeRequiredNotes
amountintegeryesMinor units. Tax- and shipping-inclusive.
currencystringnoISO 4217. Default NPR.
tax / shippingintegernoDefault 0. Must satisfy tax + shipping <= amount.
orderIdstringnoYour order id. Echoed on verify / webhooks (browser return uses our id).
referencestringno Unique per merchant. If omitted: {orderNumber||orderId||"order"}::{ourUuid}. Reuse → 409.
messagestringnoShown on checkout.
productsarrayno* Line items on checkout. When Awecount sync is on: required, each line needs code (see Awecount / IRD).
discountintegerno Order-level commercial discount (tax-inclusive). Payable amount is already net. Awecount header Amount.
customerobjectno Buyer billing for IRD (name, email, phone). Not in data.
remarksstringnoAwecount voucher remarks (not checkout message). Not in data.
dataobjectnoPass-through. Echoed on verify / webhooks. Not shown on checkout.
method"cash"no Omit for hosted checkout. "cash" = already collected; order Success + optional IRD (merchant must have cash enabled).
tagsstring[]noDashboard labels. Immutable after create.
successRedirectUrl / failureRedirectUrlurino Per-order override of merchant defaults. If set, merchant-level redirects are unused for that order.
expiresAtdatetimeno Optional. Must be in the future. Null = no expiry. Past → checkout refuses; status may move to Expired.

Success response

{
  "id": "019f8c2a-7b1e-7000-8000-abcdef012345",
  "reference": "1000123",
  "checkoutUrl": "https://pay.awecode.com/xY9kQm2..."
}

Redirect the customer to checkoutUrl. Store id for verify / cancel.

Get order

GET /api/orders/{id}

Returns current status, latest transaction pointers, and your data / tags. Also includes refunds and refundedTotal from order_refunds (the sale row is never mutated on return). For fulfillment, call verify - do not fulfill from get alone.

Cancel order

POST /api/orders/{id}/cancel

Only while Open. Already paid orders cannot be canceled (409).

Sales return (credit note)

Cash return against a Success parent sale (partial or full). Stores a row in order_refunds — no new order/transaction — and posts an Awecount credit note when sync is on. Parent sale stays Success.

POST /api/orders/{id}/return

{id} is the original payment-service order id (the paid sale).

{
  "method": "cash",
  "amount": 28000,
  "tax": 3212,
  "discount": 0,
  "reference": "019f-adjustment-uuid",
  "remarks": "Return Veg Biryani",
  "products": [
    {
      "name": "Veg Biryani",
      "quantity": 1,
      "amount": 28000,
      "code": "FOOD-biryani-01",
      "category": { "name": "Food" },
      "tax": { "rate": 13 }
    }
  ]
}
FieldRequiredNotes
methodyesMust be "cash" (cashEnabled required).
amount / productsyesReturned lines only. Product codes required when Awecount sync is on.
referenceyesIdempotency key (unique per merchant). Same ref + different payload → 409.

Response is the refund face + receipt (credit-note bill when synced), fulfilled: true. Cancel does not substitute for return.

Order exchange (CN + INV + net settle)

Fanpark-style paid-order amend: optional cash return (CN) + optional cash sale (INV at full add amount) + optional net digital/cash settle. Settle never posts a third Awecount sales voucher.

POST /api/orders/{id}/exchange
{
  "reference": "019f-adjustment-uuid",
  "remarks": "Fanpark order #48291365 adjustment",
  "return": {
    "amount": 28000,
    "tax": 3212,
    "products": [
      { "name": "Veg Biryani", "quantity": 1, "amount": 28000, "code": "FOOD-biryani-01" }
    ]
  },
  "sale": {
    "amount": 30000,
    "tax": 3442,
    "products": [
      { "name": "Samosa (2 pc)", "quantity": 3, "amount": 30000, "code": "FOOD-samosa-01" }
    ]
  },
  "settle": {
    "method": "digital",
    "successRedirectUrl": "https://pos.example/adjust?paid=1",
    "failureRedirectUrl": "https://pos.example/adjust?status=Failed"
  }
}

settleAmount = max(0, sale.amount − return.amount) (server-computed). Example above → QR for Rs 20 while IRD still gets CN 280 + INV 300.

LegAwecount (sync on)Money
Returncredit-note/Till handback (cash)
Salesales-voucher/ (cash)Fiscal INV only — not the QR amount
Settlenone (exchange_settle)Gateway/till collects net only
{
  "id": "019f-exchange-uuid",
  "reference": "019f-adjustment-uuid",
  "parentOrderId": "019f-parent-sale-uuid",
  "status": "Open",
  "settleAmount": 2000,
  "settleOrderId": "019f-settle-uuid",
  "checkoutUrl": "https://pay.awecode.com/…",
  "fulfilled": false,
  "return": { "id": "019f-refund-uuid", "amount": 28000, "receipt": { "billNo": "CN-…" } },
  "sale": { "id": "019f-sale-uuid", "amount": 30000, "receipt": { "billNo": "INV-…" } }
}
  • fulfilled: true when settleAmount is 0, settle was cash, or settle order is Success.
  • Digital settle: poll/verify POST /api/orders/{settleOrderId}/verify like any digital order; payment_success webhook fires on settle Success.
  • Same reference resumes incomplete/Failed legs; different payload → 409. Works with Awecount sync off (local bookkeeping + settle only).
data.kind is reserved
Do not send data.kind on create. PS stamps exchange_sale (INV, still vouchers) and exchange_settle (net checkout, never a third voucher) internally.

Browser return

After payment, the customer is redirected to the order's success/failure URL (per-order override, else merchant default). Params are appended with URLSearchParams (existing ?… becomes &…, never a second ?):

?id={our order UUID}&status={Success|Failed|Canceled|Expired}

Use id for POST /api/orders/{id}/verify. Order statuses: Open, Success, Failed, Canceled, Expired (American spelling).

Important
Never trust the browser redirect alone. Query params can be spoofed. Always call verify before fulfillment.

Verify payment

Required before fulfillment. Re-checks the gateway when needed and returns whether you may fulfill.

POST /api/orders/{id}/verify
Rule
Deliver only when fulfilled is true.

Paid response

{
  "id": "019f8c2a-7b1e-7000-8000-abcdef012345",
  "orderId": "1000123",
  "reference": "1000123",
  "amount": 150000,
  "currency": "NPR",
  "status": "Success",
  "verified": true,
  "fulfilled": true,
  "verificationResult": "Success",
  "transactionId": "019f8c2b-1111-7000-8000-abcdef012345",
  "gateway": "esewa",
  "gatewayReference": "000ABC"
}

Webhooks

Optional. Ask Awecode to set your success / failure webhook URLs and signing secret. Same URL for both is fine; branch on event. Still call verify before fulfillment.

  • payment_success → success webhook URL
  • payment_failed → failure webhook URL
  • receipt_synced → success webhook URL (not the redirect), when Awecount sync is on

Headers

Content-Type: application/json
X-Event-ID: <uuid, stable across retries>
X-Webhook-Signature: sha256=<hex HMAC of raw body>

HMAC key = your webhook signing secret as UTF-8 (do not base64-decode). Respond 2xx. Use X-Event-ID as an idempotency key across retries.

Retries

Up to 6 attempts. Backoff after failure: 1m, 5m, 15m, 1h, 4h, then Exhausted.

Body

{
  "event": "payment_success",
  "id": "019f8c2a-7b1e-7000-8000-abcdef012345",
  "orderId": "1000123",
  "transactionId": "019f8c2b-1111-7000-8000-abcdef012345",
  "amount": 150000,
  "currency": "NPR",
  "status": "Success",
  "gateway": "esewa",
  "gatewayReference": "000ABC",
  "data": { "yourOrderPk": 12345 },
  "tags": ["vip", "color:red"]
}

id is our order UUID (use for verify). orderId is your create-time id if set, otherwise our UUID. amount is minor units.

Vouchers

Optional. Ask Awecode to enable vouchers and set your voucherApplyUrl. Hosted checkout then shows a voucher field. You own the codes - we call your URL on apply / remove.

Tip
Same HMAC secret as webhooks. Verify X-Webhook-Signature on the raw body before trusting the request.

Only one voucher at a time. To change code, remove first, then apply again. Stacking discounts is not supported.

Apply request (us → you)

POST {your voucherApplyUrl}
Content-Type: application/json
X-Webhook-Signature: sha256=<hex HMAC of raw body>
{
  "action": "apply",
  "orderId": "019f8c2a-7b1e-7000-8000-abcdef012345",
  "merchantOrderId": "1000123",
  "reference": "1000123",
  "checkoutToken": "xY9kQm2...",
  "code": "SAVE20",
  "amount": 150000,
  "originalAmount": 150000,
  "currency": "NPR",
  "tax": 0,
  "shipping": 0,
  "data": { "yourOrderPk": 12345 }
}

You can ignore fields you do not need. Typical use:

  • code - voucher to apply
  • merchantOrderId / data - find the order in your system
  • originalAmount - create-time total (baseline for % off / min-order rules)
  • amount - current payable (same as originalAmount on apply, since only one voucher is allowed)

orderId, reference, checkoutToken, tax, shipping, currency are context if you want them.

Success response (you → us)

{
  "ok": true,
  "amount": 120000,
  "code": "SAVE20"
}

Return the new payable in amount (minor units). It must be <= originalAmount. We store that as the order payable and keep the create-time total so remove can restore it.

Failure response

{
  "ok": false,
  "error": "invalid_code",
  "detail": "Optional short message shown to the customer"
}

Common error values: invalid_code, expired, already_used, not_applicable.

Remove

Same URL with "action": "remove". Reply { "ok": true }. We restore the create-time amount locally.

100% off

If apply returns amount: 0, checkout completes without a gateway: order becomes Success, we send a payment_success webhook (gateway: "voucher"), and the customer is redirected to your success URL. Still call verify and deliver only when fulfilled: true.

Awecount / IRD

Optional. When Awecode enables Awecount sync for your merchant, a successful payment creates an Awecount sales voucher (IRD receipt). You never call Awecount yourself — send the fields below on POST /api/orders; we POST the partner sales-voucher after the order is Success.

Ask Awecode first
Merchant needs awecountSyncEnabled, companySlug, API key, and cashEnabled for cash / return / exchange. Optional gatewayModes → payment_mode ids; else payment_mode_obj by name. Tax/unit/category come from order products as Partner objects (tax.rate, unit pcs, category.name) — no merchant ids.

Payment modes (Awecount)

Prefer optional gatewayModes ids when company mode names differ. Otherwise we send payment_mode_obj: { name } (exact spelling below). Seed only creates Cash.

PS gatewayFallback Awecount name
cash, voucherCash
esewaeSewa
khaltiKhalti
fonepay_qr, fonepay_bankFonepay
connectipsConnectIPS
paypalPayPal
stripeStripe

Sample create (cash + IRD)

Payable amount is already net of discounts (here Rs 550). Gross lines 300+500 − line 50 − order 200 → amount: 55000.

POST /api/orders
{
  "amount": 55000,
  "tax": 7200,
  "currency": "NPR",
  "orderId": "10001234",
  "orderNumber": "10001234",
  "message": "Table 4",
  "discount": 20000,
  "remarks": "Order #10001234",
  "customer": {
    "name": "Ram Bahadur",
    "email": "ram@example.com",
    "phone": "9800000000"
  },
  "products": [
    {
      "name": "Veg Biryani",
      "quantity": 1,
      "amount": 30000,
      "discount": 5000,
      "code": "FOOD-biryani-01",
      "category": { "name": "Food" },
      "tax": { "rate": 13 },
      "description": "NPR 300 each"
    },
    {
      "name": "General ticket",
      "quantity": 1,
      "amount": 50000,
      "code": "TIX-general-01",
      "category": { "name": "Tickets" },
      "tax": { "rate": 13 },
      "description": "Entry"
    }
  ],
  "data": { "yourOrderPk": 12345 },
  "tags": ["fanpark"],
  "method": "cash"
}
Currency + VAT
You send tax-inclusive paisa plus order tax (VAT peel of amount). We POST Awecount as Tax Exclusive: rates and Amount discounts are scaled by (amount − tax) / amount — no hardcoded 13% — then Awecount re-adds VAT so voucher total matches your payable. Prefer currency NPR when sync is on.

Order-level fields

FieldTypeRequiredDescription
amountintegeryesPayable NPR paisa (tax-inclusive), already net of discounts.
taxintegernoDefault 0. > 0 → VAT scheme id; else taxless.
currencystringnoDefault NPR. Prefer NPR when sync is on.
orderId / orderNumberstringnoYour ids; echoed on verify / webhooks. Browser return uses our id.
messagestringnoCheckout display only (not Awecount remarks).
discountintegerno Order-level discount (tax-inclusive paisa). Awecount header Amount is VAT-peeled when tax > 0. Default 0.
remarksstringnoAwecount voucher remarks (max 500). Separate from message.
productsarrayyes*Required when sync is on. Non-empty. See product fields.
customerobjectnoBuyer billing for IRD. See customer fields.
dataobjectnoPass-through only (your PKs). Echoed on verify / webhooks.
tagsstring[]noDashboard labels. Not sent to Awecount.
method"cash"no"cash" → Success + await receipt (checkoutUrl: null). Omit for digital. Needs cashEnabled.

Customer object

FieldTypeRequiredDescription
customer.namestringno→ customer_name. Max 120. Omit → "Customer".
customer.emailstringno→ Awecount email. Max 254.
customer.phonestringno→ Awecount contact_no. Max 32.
customer.taxIdstringnoNepal PAN / VAT (9 digits). → Partner party_obj.tax_identification_number for VAT-claimable bills.

Product object

FieldTypeRequiredDescription
namestringyesCheckout title; Awecount item name on create.
quantityintegeryesPositive. Voucher row quantity.
amountintegeryes Line gross total (paisa, tax-inclusive), not unit price. Awecount row rate scales by (order.amount − order.tax) / order.amount when tax > 0, else amount / quantity / 100.
codestringyes* Stable item code (1–64). Required when sync on. Lookup by code; create if missing. Same SKU → same code forever (e.g. FOOD-<id>).
categoryobjectno{ "name": "Food", "hsCode"? } → Partner category_obj + category create (optional hs_code). Also sets item HS when present. Omit → uncategorized. Do not send legacy categoryId integers.
taxobjectno{ "rate": 13 } → Partner row tax_scheme_obj.rate. Omit → no row tax scheme.
descriptionstringnoCheckout subtitle; voucher row description (else name).
discountintegerno Line discount (tax-inclusive paisa), ≤ line amount. Awecount row Amount is VAT-peeled when tax > 0.
code vs category
code = item identity (lookup/create). category = { name, hsCode? } on create; omit leaves item uncategorized. Legacy categoryId PKs are not accepted. Unit is always unit_obj { short_name: pcs }.

Cash create response

With method: "cash" and sync enabled, create returns the verify-shaped face immediately (no checkoutUrl). When Awecount succeeds in time, receipt.status is synced and invoice is filled.

{
  "id": "019f8c2a-7b1e-7000-8000-abcdef012345",
  "orderId": "10001234",
  "orderNumber": "10001234",
  "reference": "10001234",
  "amount": 55000,
  "currency": "NPR",
  "status": "Success",
  "data": { "yourOrderPk": 12345 },
  "tags": ["fanpark", "tender:cash"],
  "customer": {
    "name": "Ram Bahadur",
    "email": "ram@example.com",
    "phone": "9800000000"
  },
  "verified": true,
  "fulfilled": true,
  "verificationResult": "Success",
  "transactionId": "019f8c2b-1111-7000-8000-abcdef012345",
  "gateway": "cash",
  "gatewayReference": null,
  "checkoutUrl": null,
  "receipt": {
    "billNo": "SV-1042",
    "status": "synced",
    "syncedAt": "2026-10-14T06:00:00.000Z",
    "invoice": {
      "id": 1042,
      "billNo": "SV-1042",
      "fiscalYear": "2082/83",
      "date": "2026-10-14",
      "customerName": "Ram Bahadur",
      "rows": [
        { "id": 1, "description": "NPR 300 each" },
        { "id": 2, "description": "Entry" }
      ]
    }
  }
}

Response / receipt fields

FieldDescription
fulfilledFulfill only when true (same rule as verify).
gatewaycash for cash tender; otherwise the digital gateway used.
checkoutUrlnull on cash create; otherwise redirect the customer there.
customerEcho of what you sent on create (name / email / phone).
receipt Present when sync is enabled. null / omitted if not claimed yet.
receipt.billNoAwecount voucher number (e.g. SV-1042) once synced.
receipt.statuspending | synced | failed.
receipt.syncedAtISO timestamp when status became synced.
receipt.invoice Print subset from Awecount: id, billNo, fiscalYear, date, customerName, lines (name / hsCode / qty / rate), legacy rows with flattened hs_code. Stored after sync; later verify does not re-POST.

Digital flow

  1. Create without method → get checkoutUrl.
  2. Customer pays → you POST /api/orders/{id}/verify (or handle webhook, then still verify).
  3. After Success we await Awecount sync before the checkout redirect (and before your POST …/verify returns). You can also wait for receipt_synced.

receipt_synced webhook

Delivered to your success webhook URL after the voucher is created (in addition to payment_success). Same HMAC headers as other webhooks.

{
  "event": "receipt_synced",
  "orderId": "10001234",
  "transactionId": "019f8c2b-1111-7000-8000-abcdef012345",
  "amount": 55000,
  "currency": "NPR",
  "status": "Success",
  "gateway": "cash",
  "gatewayReference": null,
  "data": { "yourOrderPk": 12345 },
  "tags": ["fanpark", "tender:cash"],
  "receipt": {
    "billNo": "SV-1042",
    "status": "synced",
    "syncedAt": "2026-10-14T06:00:00.000Z",
    "invoice": {
      "id": 1042,
      "billNo": "SV-1042",
      "fiscalYear": "2082/83",
      "date": "2026-10-14",
      "customerName": "Ram Bahadur",
      "rows": []
    }
  }
}

Create rejects (sync on)

  • Empty products or any line missing code → 400.
  • data.remarks present → 400 (use top-level remarks).
  • data.kind present → 400 (reserved for exchange legs; PS sets it).
  • method: "cash" but cash not enabled → 403.

Credit note & exchange

Beyond create sales vouchers:

  • Sales return → Partner credit-note/ linked to the parent INV.
  • Order exchange → CN + INV at full line amounts; settle checkout never posts a third voucher (exchange_settle). Sale leg still posts INV (exchange_sale is an internal stamp only).

Repo detail: docs/integrations/awecount.md and docs/order-exchange.md.

Errors

{
  "statusCode": 409,
  "statusMessage": "Order reference already exists for this merchant",
  "message": "Order reference already exists for this merchant"
}

Quick curl

BASE=https://pay.awecode.com
KEY=ps_...

curl -sS -X POST "$BASE/api/orders" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount":150000,"currency":"NPR","orderId":"1000123"}'

curl -sS "$BASE/api/orders/$ORDER_ID" -H "Authorization: Bearer $KEY"
curl -sS -X POST "$BASE/api/orders/$ORDER_ID/verify" -H "Authorization: Bearer $KEY"

Security

Payment integration bugs become money bugs. Treat fulfillment as a security boundary: only your backend, only after server-to-server verify, only when fulfilled: true.

Non-negotiable
Browser return and unsigned webhooks are not proof of payment. Always verify server-to-server before shipping or granting access.

Checklist

Tick each item as you implement. Saved in this browser only.

Thuprai Pay

Every payment method in one checkout. Money settles to your own accounts.

© 2026 Awecode