Skip to Content
Merchant Onboarding

Merchant Onboarding

This guide is for merchant engineers integrating HMAC /v1/* against https://api.transacty.ai. Portal JWT (/portal/*) is a different product — do not mix it into this integration.

What you get from Transacty

When a merchant is onboarded, they receive:

  • apiKey
  • secret
  • baseUrlhttps://api.transacty.ai

These credentials are used to call the Transacty APIs.

There is no separate sandbox hostname. Create a test key or a live key in the dashboard. Every /v1 call runs in that key’s environment (separate wallets and transactions).

Base URL

https://api.transacty.ai

Test and live use the same base URL and paths. The API key chooses the environment.

Headers (every /v1/* request)

HeaderValue
X-Transacty-KeyAPI key (transacty_…)
X-Transacty-TimestampUnix time in seconds (not ms)
X-Transacty-SignatureHMAC-SHA256 of {timestamp}.{rawBody}, lowercase hex
Content-Typeapplication/json on POST/PATCH
Idempotency-KeyRequired on money writes (create pay-in / payout / payment intent / NGN virtual-account provision). Unique per create. Replaying the same key + same body returns the first response.

Signing payload = timestamp + a literal . + the exact raw body bytes.

  • POST/PATCH: sign the JSON string you send (no re-serialize after signing).
  • GET: body is empty → payload is {timestamp}. (trailing dot).
  • Replay window: ±5 minutes. Sign at send time; do not cache signatures.
import crypto from "node:crypto"; function sign(secret, timestamp, rawBody) { return crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`, "utf8").digest("hex"); } const timestamp = Math.floor(Date.now() / 1000).toString(); const rawBody = JSON.stringify({ amount: "500.00", paymentMethodCode: "BKASH" /* … */ }); const signature = sign(process.env.TRANSACTY_SECRET, timestamp, rawBody); await fetch("https://api.transacty.ai/v1/payins", { method: "POST", headers: { "Content-Type": "application/json", "X-Transacty-Key": process.env.TRANSACTY_KEY, "X-Transacty-Timestamp": timestamp, "X-Transacty-Signature": signature, "Idempotency-Key": crypto.randomUUID(), }, body: rawBody, });

Missing Idempotency-Key on a create → 400 { "error": "Bad Request", "message": "Idempotency-Key header is required" }.

Create pay-ins and payouts from your server only (HMAC). The dashboard uses a separate login (JWT) and never holds the API secret.

Scopes

Typical live key: payin:create, payout:create, balance:read. * is for non-production only.

Markets

Each region is a separate KYB market (bangladesh, brazil, india, europe, nigeria, pyusd). Until that market is approved, those /v1 routes return 403 with code: "market_not_enabled".

Status values

Use lowercase: pending, success, failed. Do not use PENDING / SUCCESS as Transacty statuses.

Settlement wallets (never mix)

RegionCollectMerchant pocket (currency)Display
BangladeshBDT e-walletsBDTBDT
BrazilPIXBRLBRL
India H2HINR via UPIUSDTUSDT
EuropeEUR/GBP Open BankingUSDCUSDC
NigeriaPermanent NGN virtual accountNGNNGN
PYUSDPYUSD on EthereumPYUSD-USDCPYUSD USDC

PYUSD USDC is not Europe USDC. EUR payouts and Europe spend debit USDC only. They cannot spend PYUSD-USDC. Do not treat PYUSD as settling to your Europe USDC wallet.

Never sum unlike currencies into one headline number.

Test vs live

testlive
How you chooseTest API keyLive API key
Portalenvironment: "test"environment: "live"
MoneyIsolated ledgerProduction money
PYUSDNot available (503 payment_unavailable)Only supported mode
Nigeria (NGN)Not available (503 payment_unavailable)Only supported mode

Bangladesh availability

POST /v1/payins and POST /v1/payouts may return 503 { "code": "payment_unavailable" } while the Bangladesh rail is paused. Brazil, India, Europe, Nigeria, and PYUSD are independent. Do not use Bangladesh as the only integration test while that pause is on.

Common errors

HTTPcodeMeaning
400Validation, or missing Idempotency-Key
401Bad key, signature, or timestamp outside ±5 min
403market_not_enabledThat region is not approved
403Missing scope
503payment_unavailableRail paused, upstream down, or NGN / PYUSD test key
409Same Idempotency-Key with a different body

Regions and rails

The same API key and the same transaction list / webhook URL serve all rails — only the API paths and checkout UX differ.

RegionRailPay-in APISettlement walletCheckout
Bangladeshbangladesh/v1/payinsBDTProvider link from paymentInfo.content
Brazilbrazil/v1/br/payinsBRLPIX from paymentInfo
Indiaindia/v1/h2h/*USDTMerchant-built UPI UI
Europeeurope/v1/eur/*USDCRedirect to checkoutUrl
Nigerianigeria/v1/ngn/*NGNPermanent virtual account (share bank details)
PYUSDpyusd/v1/pyusd/payment-intentsPYUSD-USDCEthereum deposit address

Main endpoints merchants use

Auth / identity

  • GET /v1/me

Balance

  • GET /v1/balance

Bangladesh pay-in / payout

  • POST /v1/payins · GET /v1/payins/:id
  • POST /v1/payouts · GET /v1/payouts/:id

Brazil PIX

  • POST /v1/br/payins · GET /v1/payins/:id
  • POST /v1/br/payouts · GET /v1/payouts/:id

India UPI (H2H)

  • POST /v1/h2h/payin-instances · GET /v1/h2h/payin-instances/:transactionId
  • POST /v1/h2h/buyer-confirms-payment

Europe Open Banking

  • POST /v1/eur/payin-instances · GET /v1/eur/payin-instances/:transactionId
  • POST /v1/eur/payout-instances · POST /v1/eur/payout-instances/:transactionId/approve

Nigeria (NGN virtual account)

  • GET /v1/ngn/virtual-account · POST /v1/ngn/virtual-account
  • GET /v1/ngn/banks · POST /v1/ngn/verify-account
  • POST /v1/ngn/payouts · GET /v1/ngn/payouts/:transactionId

PYUSD (Ethereum → PYUSD USDC)

  • POST /v1/pyusd/payment-intents · GET /v1/pyusd/payment-intents/:transactionId

Transactions

  • GET /v1/transactions (filter by type; then filter rail / status in your app)
  • GET /v1/transactions/:transactionId

Webhooks

  • PATCH /v1/me/webhook

Important notes for merchants

  • Use transactionId from Transacty as your main reference.
  • platformOrderId is the provider-side reference (for support / escalation).
  • Do not call any payment provider directly from your integration.
  • Each currency is a separate wallet pocket; pendingBalance is in-flight pay-ins.
  • Poll the relevant status endpoint when you need the latest state, or wait for webhooks.

First integration test

Do not rely on Bangladesh as the only smoke test while that rail may be paused (503 payment_unavailable). Use a region whose market is approved:

Nigeria and PYUSD have no test mode. Use a live key only — a test key always fails closed (503 payment_unavailable).

If Bangladesh is available:

Check identity

GET /v1/me

Check balance

GET /v1/balance

Create a pay-in

POST /v1/payins with Idempotency-Key, then complete the payer step from paymentInfo.content

Poll until success

GET /v1/payins/:id until success

Create a payout

POST /v1/payouts with Idempotency-Key, then GET /v1/payouts/:id until a final state

How to go live

Complete KYC

Activate your account in the portal.

Get each region approved

Each market (bangladesh, brazil, india, europe, nigeria, pyusd) is a separate KYB approval.

Create API keys

Create a test key, then a live key when ready. Grant payin:create, payout:create, and balance:read.

Sign every /v1 call

Send Idempotency-Key on creates (including NGN virtual-account provision).

Configure your webhook

PATCH /v1/me/webhook (HTTPS). Handle payin.completed / payin.failed.

Nigeria and PYUSD are live-only

For Nigeria (NGN) and PYUSD use the live key only. Do not expect a test key to work.

Last updated on