Integrate Taria Pay (for coding agents)
This page is the single contract for coding agents and humans wiring Taria Pay into a merchant app. Do not invent field names, paths, or checkout URLs.
Related human guides: Quickstart · Create a payment · Webhooks · API reference
Environment
| What | Value |
|---|---|
| Production API origin | https://api.tariapay.com |
| Local API origin | http://127.0.0.1:3003 |
| Hosted checkout | https://checkout.tariapay.com (local: http://127.0.0.1:3004) |
| Env: secret key | TARIAPAY_SECRET_KEY (Dashboard → Developers). Alias: TARIAPAY_API_KEY |
| Env: webhook secret | TARIAPAY_WEBHOOK_SECRET |
| Env: API origin | TARIAPAY_API_BASE — origin only, no path |
Merchant routes:
| Caller | URL |
|---|---|
| Canonical | {origin}/v1/payment-intents |
| Legacy alias (same handlers) | {origin}/merchant/v1/payment-intents |
SDK baseUrl | pass {origin} only. The SDK appends /v1. |
Do not combine prefixes (/merchant/v1 plus an extra /v1). Public checkout routes stay at /checkout/v1/... and are not for merchant servers.
Minimal flow
- Server creates a payment intent with the secret key.
- Browser redirects to
paymentIntent.checkoutUrl(orCheckoutButtonopens it). - Fulfill only after a verified webhook (
payment_intent.confirmed) or a server-sideretrieve. - Never fulfill because the buyer hit
successUrl.
Node / Workers skeleton
import { TariaPay, TariaPayError, constructWebhookEvent } from "@tariapay/sdk";
const tariapay = new TariaPay({
secretKey: process.env.TARIAPAY_SECRET_KEY!,
baseUrl: process.env.TARIAPAY_API_BASE, // origin only
});
export async function createCheckout(order: {
orderId: string;
amount: string;
email?: string;
appUrl: string;
}) {
try {
return await tariapay.paymentIntents.create({
orderId: order.orderId,
amount: order.amount,
currency: "USDC",
acceptedCurrencies: ["USDC", "USDT"],
successUrl: `${order.appUrl}/orders/${order.orderId}?pay=success`,
cancelUrl: `${order.appUrl}/orders/${order.orderId}?pay=cancel`,
customer: order.email ? { email: order.email } : undefined,
});
} catch (error) {
if (error instanceof TariaPayError && error.type === "timeout_error") {
return tariapay.paymentIntents.retrieveByOrderId(order.orderId);
}
throw error;
}
}
export function verifyWebhook(rawBody: string, headers: Headers) {
return constructWebhookEvent(rawBody, headers, process.env.TARIAPAY_WEBHOOK_SECRET!);
}Cloudflare Workers:
- Enable
nodejs_compat. - The SDK binds
fetchfor you. If you inject fetch, wrap it:(input, init) => fetch(input, init). constructWebhookEventdoes not need a merchant secret key.- Put secrets in
wrangler secret/.dev.vars, never[vars].
acceptedCurrencies
currency is the primary coin and is what you store on your order.
acceptedCurrencies is the checkout coin picker. If you omit it, the page shows only currency.
await tariapay.paymentIntents.create({
orderId: "order_1001",
amount: "49.99",
currency: "USDC",
acceptedCurrencies: ["USDC", "USDT"],
});paymentMethods controls wallet vs transfer, not coins.
Webhook contract
Headers (canonical and alias — accept both):
| Canonical | Alias | Meaning |
|---|---|---|
x-tariapay-signature | x-pay-signature | hex HMAC-SHA256 of ${timestamp}.${rawBody} |
x-tariapay-timestamp | x-pay-signature-timestamp | unix seconds |
x-tariapay-event | x-pay-event | event type |
x-tariapay-delivery-id | x-pay-delivery-id | delivery id (dedupe key) |
{
"id": "evt_123",
"type": "payment_intent.confirmed",
"createdAt": "2026-03-29T12:00:00.000Z",
"data": {
"paymentIntent": {
"paymentId": "b1985d0b-2026-4f8c-bf75-59ea53fe5466",
"orderId": "order_1001",
"status": "confirmed",
"currency": "USDC",
"amount": "49.99",
"checkoutUrl": "https://checkout.tariapay.com/pi_b1985d0b-2026-4f8c-bf75-59ea53fe5466",
"txHash": "0x..."
}
}
}Read data.paymentIntent. Dedupe on x-tariapay-delivery-id or id. Fulfill only when type === "payment_intent.confirmed".
Local E2E (commands)
From the Taria Pay repo:
npm run dev:backend # :3003
npm run dev:checkout # :3004
./scripts/dev-backfill-loop.shOn the merchant app:
TARIAPAY_API_BASE=http://127.0.0.1:3003
TARIAPAY_SECRET_KEY=tpk_...
# Webhook: public HTTPS tunnel to your handler, then register it in Dashboard → Developers → WebhooksA paid intent stays processing until /internal/backfill runs. Without the backfill loop, no merchant webhook fires locally.
Test-mode payments on hosted testnets need test tokens: contract addresses and the faucet flow are in Testing → Test tokens.
Error map
| Symptom | What to do |
|---|---|
| Create 401 | Wrong key, or baseUrl is not the origin (or the listener has no /v1 alias). |
| Create timeout | retrieveByOrderId(orderId) — do not mint a new orderId. |
| Create 409 | orderId already exists; retrieve it. |
| Checkout shows one coin | Pass acceptedCurrencies. |
| Webhook signature fails | HMAC the raw body; secret must match the registered endpoint. |
| Paid but no webhook locally | Start dev-backfill-loop.sh; expose the handler with a tunnel. |
Workers Illegal invocation on fetch | Don't pass unbound globalThis.fetch; use the SDK default or wrap it. |
Frontend
Hosted redirect: window.location.assign(checkoutUrl).
React button:
import { CheckoutButton } from "@tariapay/checkout-panel/button";
import "@tariapay/checkout-panel/styles.css";Vite extras: optimizeDeps.include: ["@tariapay/checkout-panel/button"], and server.fs.allow if the package is a linked source workspace. The button does not pull in a wallet stack.
Do not
- Put the secret key in a browser bundle.
- Fulfill from
successUrl. - Construct
checkout.tariapay.com/...yourself. - Retry a timed-out create with a new
orderId. - Guess webhook field paths (
data.payment/ snake_case). The envelope above is the contract.