Skip to content

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

WhatValue
Production API originhttps://api.tariapay.com
Local API originhttp://127.0.0.1:3003
Hosted checkouthttps://checkout.tariapay.com (local: http://127.0.0.1:3004)
Env: secret keyTARIAPAY_SECRET_KEY (Dashboard → Developers). Alias: TARIAPAY_API_KEY
Env: webhook secretTARIAPAY_WEBHOOK_SECRET
Env: API originTARIAPAY_API_BASEorigin only, no path

Merchant routes:

CallerURL
Canonical{origin}/v1/payment-intents
Legacy alias (same handlers){origin}/merchant/v1/payment-intents
SDK baseUrlpass {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

  1. Server creates a payment intent with the secret key.
  2. Browser redirects to paymentIntent.checkoutUrl (or CheckoutButton opens it).
  3. Fulfill only after a verified webhook (payment_intent.confirmed) or a server-side retrieve.
  4. Never fulfill because the buyer hit successUrl.

Node / Workers skeleton

ts
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 fetch for you. If you inject fetch, wrap it: (input, init) => fetch(input, init).
  • constructWebhookEvent does 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.

ts
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):

CanonicalAliasMeaning
x-tariapay-signaturex-pay-signaturehex HMAC-SHA256 of ${timestamp}.${rawBody}
x-tariapay-timestampx-pay-signature-timestampunix seconds
x-tariapay-eventx-pay-eventevent type
x-tariapay-delivery-idx-pay-delivery-iddelivery id (dedupe key)
json
{
  "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:

bash
npm run dev:backend          # :3003
npm run dev:checkout         # :3004
./scripts/dev-backfill-loop.sh

On the merchant app:

bash
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 → Webhooks

A 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

SymptomWhat to do
Create 401Wrong key, or baseUrl is not the origin (or the listener has no /v1 alias).
Create timeoutretrieveByOrderId(orderId) — do not mint a new orderId.
Create 409orderId already exists; retrieve it.
Checkout shows one coinPass acceptedCurrencies.
Webhook signature failsHMAC the raw body; secret must match the registered endpoint.
Paid but no webhook locallyStart dev-backfill-loop.sh; expose the handler with a tunnel.
Workers Illegal invocation on fetchDon't pass unbound globalThis.fetch; use the SDK default or wrap it.

Frontend

Hosted redirect: window.location.assign(checkoutUrl).

React button:

tsx
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.