Checkout


The Checkout API lets you collect payments directly from your website or app using Tsara's secure, pre-built payment interface.

You can:

  • Redirect customers to a hosted Checkout page, or
  • Embed Checkout directly in your app with a simple JavaScript snippet.

Tsara handles the entire payment flow — from customer input to authorization and verification — so you can focus on your product.


Overview

FeatureDescription
Hosted CheckoutRedirect customers to a Tsara-hosted payment page.
Embedded CheckoutLaunch Checkout directly inside your app using JavaScript.
Supported MethodsCard, Bank Transfer, Stablecoin (USDC).
CurrenciesNGN, USDC.
WebhooksReal-time events for payment.success, payment.failed.

Key Differences: Hosted vs Embedded

FeatureHosted CheckoutEmbedded Checkout
IntegrationSimple redirect to Tsara URLRequires JavaScript SDK
User ExperienceFull page redirectModal/popup on your site
CustomizationLimited (logo, colors via dashboard)Full control over trigger button
Mobile FriendlyYesYes
Use CaseQuick integration, email linksSeamless in-app experience

Create a Checkout Session

Generate a new checkout session that can be used for hosted or embedded checkout.

Endpoint

POST /checkout/create

Headers

HeaderValueRequired
AuthorizationBearer YOUR_SECRET_KEYYes
Content-Typeapplication/jsonYes

Request Parameters

ParameterTypeRequiredDescriptionExample
amountnumberYesPayment amount in minor units for NGN (kobo) or full units for USDC200000 (₦2,000.00)
currencystringYesCurrency code. Accepts: NGN, USDC"NGN"
trx_idstringYesYour unique transaction reference (max 100 chars, alphanumeric, hyphen, underscore)"order_001"
public_keystringYesYour Tsara public key (starts with pk_test_ or pk_live_)"pk_test_abc123..."
emailstringYesCustomer email address (valid email format)"[email protected]"
namestringNoCustomer full name (max 100 chars)"John Doe"
phonestringNoCustomer phone number (Nigerian format: 11 digits starting with 0)"08012345678"
redirect_urlstringNoURL to redirect after payment (any status). Overrides success_url and cancel_url"https://example.com/callback"
success_urlstringNoURL to redirect after successful payment (used if redirect_url not set)"https://example.com/success"
cancel_urlstringNoURL to redirect if payment cancelled or failed (used if redirect_url not set)"https://example.com/cancel"
metaobjectNoCustom metadata for tracking (max 10 keys, 500 chars per value){"order_id": "ORD-123"}
customerobjectNoCustomer details object (alternative to individual fields)See below
customizationsobjectNoCheckout page customizations (title, description, logo)See below

Customer Object Structure

{
  "customer": {
    "email": "[email protected]",
    "phone": "08012345678",
    "name": "John Doe"
  }
}

Customizations Object Structure

{
  "customizations": {
    "title": "Your Business Name",
    "description": "Payment for Order #123",
    "logo": "https://example.com/logo.png"
  }
}
FieldTypeDescription
titlestringBusiness name shown at top of checkout (max 50 chars)
descriptionstringPayment description shown to customer (max 200 chars)
logostringURL to your logo image (HTTPS only, max 2MB, square recommended)

Important: Public Key vs Secret Key

  • Secret Key (e.g., sk_test_...) - Use for server-side API calls (this endpoint)
  • Public Key (e.g., pk_test_...) - Include in request body for session creation and use in JavaScript SDK
  • Never expose your Secret Key in frontend code

Request Example

{
  "public_key": "pk_test_0fbvh9l559sewud83m219wofxcpizxdm",
  "amount": 200000,
  "currency": "NGN",
  "trx_id": "order_001_1738318449",
  "email": "[email protected]",
  "name": "John Doe",
  "phone": "08012345678",
  "redirect_url": "https://example.com/payment-callback",
  "meta": {
    "order_id": "ORD-123",
    "customer_id": "cus_456"
  },
  "customizations": {
    "title": "Acme Store",
    "description": "Payment for Order #ORD-123",
    "logo": "https://acmestore.com/logo.png"
  }
}
curl -X POST "https://sandbox.tsara.ng/v1/checkout/create" \
  -H "Authorization: Bearer YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "public_key": "pk_test_0fbvh9l559sewud83m219wofxcpizxdm",
    "amount": 200000,
    "currency": "NGN",
    "trx_id": "order_001_1738318449",
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "08012345678",
    "redirect_url": "https://example.com/payment-callback",
    "meta": {
      "order_id": "ORD-123"
    }
  }'

Response

{
  "success": true,
  "status": "success",
  "status_code": 200,
  "message": "Checkout Created",
  "data": {
    "id": "sess_695fc9b9d4e9920024465a46",
    "status": "pending",
    "checkout_url": "https://pay.sandbox.tsara.ng/checkout/sess_695fc9b9d4e9920024465a46",
    "expires_at": "2026-01-08T16:54:09Z",
    "amount": 200000,
    "currency": "NGN",
    "account": {
      "account_number": "6028678511",
      "account_name": "STL Checkout",
      "bank_code": "090286",
      "bank_name": "Safe Haven MFB",
      "SOLANA": "FqVQHH3mSAEKXEKN4JtZeFuhosQ14B3HXyLdeT3Wqedg",
      "_id": "695fc9b9d4e9920024465a46",
      "business_uid": "66ac4ae2e5310c6d9bc615bf"
    }
  },
  "url": "https://pay.sandbox.tsara.ng/checkout/sess_695fc9b9d4e9920024465a46",
  "trx_id": "order_001_1738318449",
  "account": {
    "account_number": "6028678511",
    "account_name": "STL Checkout",
    "bank_code": "090286",
    "bank_name": "Safe Haven MFB",
    "SOLANA": "FqVQHH3mSAEKXEKN4JtZeFuhosQ14B3HXyLdeT3Wqedg",
    "_id": "695fc9b9d4e9920024465a46",
    "business_uid": "66ac4ae2e5310c6d9bc615bf"
  }
}

Response Fields

FieldTypeDescription
successbooleanRequest success status
statusstringRequest status text
status_codenumberHTTP status code
messagestringHuman-readable message
data.idstringCheckout session ID
data.statusstringSession status. Options: pending, completed, failed, expired
data.checkout_urlstringURL for hosted checkout page
data.expires_atstringSession expiration timestamp (ISO 8601)
data.amountnumberPayment amount
data.currencystringCurrency code
data.accountobjectVirtual account details for bank transfer payments
data.account.account_numberstringNigerian bank account number for transfers
data.account.account_namestringAccount name
data.account.bank_codestringNigerian bank code
data.account.bank_namestringBank name
data.account.SOLANAstringSolana wallet address for USDC payments
urlstringCheckout URL (same as data.checkout_url)
trx_idstringYour transaction reference
accountobjectAccount details (same as data.account)

Session Status Values

StatusDescription
pendingSession created, awaiting payment
completedPayment successful
failedPayment failed
expiredSession expired before payment (24 hours default)

Session Expiration

  • Default expiration: 24 hours from creation
  • After expiration, the checkout_url and virtual account become invalid
  • Create a new session for expired checkouts

Error Responses

{
  "success": false,
  "status_code": 400,
  "error": {
    "code": "validation_error",
    "message": "Invalid request parameters",
    "details": [
      {
        "field": "amount",
        "message": "Amount must be greater than 0"
      },
      {
        "field": "email",
        "message": "Invalid email format"
      }
    ]
  }
}

Common Errors

Status CodeError CodeDescription
400validation_errorInvalid request parameters (check details array)
400invalid_currencyCurrency not supported. Use NGN or USDC
400duplicate_trx_idTransaction ID already used. Use unique trx_id per checkout
400invalid_public_keyPublic key is invalid or doesn't match your account
401unauthorizedInvalid or missing Secret Key in Authorization header
429rate_limit_exceededToo many requests. Retry after delay

Hosted Checkout Flow

Redirect the customer to the checkout_url provided in the API response.

Implementation

<a href="https://pay.sandbox.tsara.ng/checkout/sess_123" class="btn btn-primary">
  Pay ₦2,000.00
</a>

Or redirect programmatically:

const response = await fetch('https://sandbox.tsara.ng/v1/checkout/create', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_SECRET_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    public_key: 'pk_test_...',
    amount: 200000,
    currency: 'NGN',
    trx_id: 'order_001',
    email: '[email protected]',
    redirect_url: 'https://example.com/callback'
  })
});

const data = await response.json();
window.location.href = data.data.checkout_url;

Payment Flow

  1. Customer clicks payment button
  2. Redirected to Tsara hosted checkout page
  3. Customer selects payment method (Card, Bank Transfer, USDC)
  4. Customer completes payment
  5. Tsara redirects back to your site

Redirect URLs

After payment, customers are redirected to:

  • redirect_url (if provided) - Used for all payment statuses
  • success_url (if redirect_url not set) - Used only for successful payments
  • cancel_url (if redirect_url not set) - Used for cancelled/failed payments

Query Parameters in Redirect

Tsara appends these parameters to your redirect URL:

https://example.com/callback?status=success&trx_id=order_001&reference=pay_123
ParameterDescription
statusPayment status: success, failed, cancelled
trx_idYour transaction reference
referenceTsara payment ID

⚠️ Always verify payment on your server - Don't trust client-side parameters alone.


Embedded Checkout (JavaScript SDK)

Embed Checkout directly inside your app using Tsara's JavaScript SDK for a seamless experience.

Step 1: Include the Script

Add the Tsara Checkout script to your HTML:

<script src="https://checkout.tsara.ng/inline.js?v=1"></script>

Script Versions

  • Latest: https://checkout.tsara.ng/inline.js?v=1
  • CDN (alternative): https://js.tsara.ng/v1/checkout.js

Step 2: Add a Payment Button

<button id="payBtn" class="btn btn-primary">Pay Now</button>

Step 3: Initialize Checkout

Method 1: Direct Initialization (Recommended)

document.getElementById('payBtn').addEventListener('click', function() {
  tsara.pay({
    public_key: "pk_test_0fbvh9l559sewud83m219wofxcpizxdm",
    trx_id: "order_" + Date.now(),
    amount: 200000,
    currency: "NGN",
    email: "[email protected]",
    phone: "08012345678",
    name: "John Doe",
    meta: {
      order_id: "ORD-123",
      customer_id: "cus_456"
    },
    customizations: {
      title: "Acme Store",
      description: "Order Payment",
      logo: "https://example.com/logo.png"
    },
    redirect_url: "https://example.com/payment-callback",
    onSuccess: function(response) {
      console.log('Payment successful:', response);
      window.location.href = '/success?ref=' + response.reference;
    },
    onCancel: function() {
      console.log('Payment cancelled');
      alert('Payment was cancelled');
    },
    onError: function(error) {
      console.error('Payment error:', error);
      alert('Payment failed: ' + error.message);
    }
  });
});

Method 2: Legacy Function Wrapper

function fundWithTsara(data) {
  tsara.pay({
    public_key: "pk_test_0fbvh9l559sewud83m219wofxcpizxdm",
    trx_id: data.trx_id,
    amount: data.amount,
    currency: "NGN",
    email: data.email,
    phone: data.phone,
    name: data.name,
    meta: {
      customer_id: data.user_id
    },
    customer: {
      email: data.email,
      phone: data.phone,
      name: data.name
    },
    redirect_url: data.redirect_url,
    customizations: {
      title: "Your Business",
      description: "Payment Description",
      logo: "https://example.com/logo.png"
    }
  });
}

document.querySelector('.pay').addEventListener('click', function() {
  fundWithTsara({
    trx_id: generateTrxId(),
    amount: 100000,
    email: '[email protected]',
    phone: '08012345678',
    name: 'John Doe',
    user_id: 'user_123',
    redirect_url: 'https://example.com/callback'
  });
});

JavaScript SDK Parameters

ParameterTypeRequiredDescription
public_keystringYesYour Tsara public key
trx_idstringYesUnique transaction reference
amountnumberYesPayment amount (minor units for NGN, full units for USDC)
currencystringYesCurrency code (NGN or USDC)
emailstringYesCustomer email
phonestringNoCustomer phone number
namestringNoCustomer full name
metaobjectNoCustom metadata
customerobjectNoCustomer details object
customizationsobjectNoCheckout customizations
redirect_urlstringNoPost-payment redirect URL
onSuccessfunctionNoCallback for successful payment
onCancelfunctionNoCallback when payment is cancelled
onErrorfunctionNoCallback for payment errors

Callback Functions

onSuccess: function(response) {
  console.log('Payment successful');
  console.log('Reference:', response.reference);
  console.log('Transaction ID:', response.trx_id);
  console.log('Amount:', response.amount);
}

onCancel: function() {
  console.log('Payment cancelled by user');
}

onError: function(error) {
  console.log('Payment error:', error.message);
  console.log('Error code:', error.code);
}

Callback Response Object

FieldTypeDescription
referencestringTsara payment reference
trx_idstringYour transaction ID
amountnumberPayment amount
currencystringCurrency code
statusstringPayment status

Complete Embedded Checkout Example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Tsara Checkout Example</title>
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="container mt-5">
    <div class="card">
      <div class="card-body">
        <h5 class="card-title">Order Summary</h5>
        <p class="card-text">Amount: ₦2,000.00</p>
        <button class="btn btn-primary" id="payBtn">Pay Now</button>
      </div>
    </div>
  </div>

  <script src="https://checkout.tsara.ng/inline.js?v=1"></script>
  <script>
    function generateTrxId() {
      return 'trx_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6);
    }

    document.getElementById('payBtn').addEventListener('click', function() {
      tsara.pay({
        public_key: "pk_test_0fbvh9l559sewud83m219wofxcpizxdm",
        trx_id: generateTrxId(),
        amount: 200000,
        currency: "NGN",
        email: "[email protected]",
        phone: "08012345678",
        name: "John Doe",
        meta: {
          order_id: "ORD-123"
        },
        customizations: {
          title: "Acme Store",
          description: "Order Payment",
          logo: "https://example.com/logo.png"
        },
        onSuccess: function(response) {
          alert('Payment successful!');
          window.location.href = '/success?ref=' + response.reference;
        },
        onCancel: function() {
          alert('Payment cancelled');
        },
        onError: function(error) {
          alert('Payment failed: ' + error.message);
        }
      });
    });
  </script>
</body>
</html>

Payment Methods

When customers reach the checkout page (hosted or embedded), they can choose from:

1. Card Payment

  • Supports Visa, Mastercard, Verve
  • Instant confirmation
  • 3D Secure authentication
  • Best for: Immediate payments

2. Bank Transfer

  • Virtual account generated for each session
  • Account details shown in checkout
  • Payment confirmed within minutes
  • Best for: Larger amounts, customers without cards

Virtual Account Details (from API response)

{
  "account_number": "6028678511",
  "account_name": "STL Checkout",
  "bank_name": "Safe Haven MFB",
  "bank_code": "090286"
}

Customers can transfer to this account via:

  • Mobile banking apps
  • USSD
  • Internet banking
  • Bank branch

3. USDC (Stablecoin)

  • Solana blockchain
  • Near-instant confirmation
  • Low transaction fees
  • Best for: Crypto-native customers, cross-border

USDC Wallet Address (from API response)

{
  "SOLANA": "FqVQHH3mSAEKXEKN4JtZeFuhosQ14B3HXyLdeT3Wqedg"
}

Important: USDC Amount Format

  • NGN uses minor units: 200000 = ₦2,000.00
  • USDC uses full units: 50 = 50 USDC (not 0.00050 USDC)

Verify Payment

Always verify payments on your server before fulfilling orders, even after receiving webhooks or redirect callbacks.

Endpoint

GET /payments?id={trx_id}

Query Parameters

ParameterTypeRequiredDescription
idstringYesYour transaction reference (trx_id)

Example Request

curl -X GET "https://sandbox.tsara.ng/v1/payments?id=order_001" \
  -H "Authorization: Bearer YOUR_SECRET_KEY"

Response

{
  "success": true,
  "status": "success",
  "status_code": 200,
  "message": "Payment retrieved",
  "data": {
    "id": "pay_695fc9b9d4e992",
    "reference": "order_001",
    "status": "success",
    "amount": 200000,
    "currency": "NGN",
    "payment_method": "card",
    "customer": {
      "email": "[email protected]",
      "phone": "08012345678",
      "name": "John Doe"
    },
    "meta": {
      "order_id": "ORD-123"
    },
    "paid_at": "2025-01-31T12:00:00Z",
    "created_at": "2025-01-31T11:55:00Z"
  }
}

Payment Status Values

StatusDescriptionAction
successPayment successfulFulfill order
pendingPayment initiated, awaiting confirmationWait for webhook or poll
failedPayment failedShow error, allow retry
cancelledUser cancelled paymentAllow retry

Verification Flow

async function verifyPayment(trxId) {
  const response = await fetch(`https://sandbox.tsara.ng/v1/payments?id=${trxId}`, {
    headers: {
      'Authorization': 'Bearer YOUR_SECRET_KEY'
    }
  });

  const data = await response.json();

  if (data.success && data.data.status === 'success') {
    await fulfillOrder(data.data.reference);
    return true;
  }

  return false;
}

Webhook Notifications

Tsara sends webhook events for every checkout payment attempt.

Event Types

EventDescription
payment.successPayment completed successfully
payment.failedPayment attempt failed
payment.pendingPayment initiated, awaiting confirmation (bank transfers)

Example Webhook Payload: payment.success

{
  "id": "evt_456",
  "type": "payment.success",
  "created_at": "2025-01-31T12:00:00Z",
  "data": {
    "payment": {
      "id": "pay_695fc9b9d4e992",
      "reference": "order_001",
      "amount": 200000,
      "currency": "NGN",
      "status": "success",
      "payment_method": "card",
      "session_id": "sess_695fc9b9d4e9920024465a46",
      "customer": {
        "email": "[email protected]",
        "phone": "08012345678",
        "name": "John Doe"
      },
      "meta": {
        "order_id": "ORD-123",
        "customer_id": "cus_456"
      },
      "paid_at": "2025-01-31T12:00:00Z"
    }
  }
}

Webhook Handler Example

const express = require('express');
const crypto = require('crypto');

app.post('/webhooks/tsara', express.raw({type: 'application/json'}), (req, res) => {
  const payload = req.body.toString();
  const signature = req.headers['x-tsara-signature'];

  const expected = crypto
    .createHmac('sha512', process.env.TSARA_WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');

  if (signature !== expected) {
    return res.status(400).send('Invalid signature');
  }

  const event = JSON.parse(payload);

  if (event.type === 'payment.success') {
    const payment = event.data.payment;
    await fulfillOrder(payment.reference, payment);
  }

  res.sendStatus(200);
});

See Webhooks Security for detailed webhook handling guide.


Use Cases & Examples

E-commerce Checkout

const cart = getShoppingCart();
const total = calculateTotal(cart);

const checkoutResponse = await createCheckoutSession({
  amount: total,
  currency: 'NGN',
  trx_id: `order_${orderId}`,
  email: customer.email,
  meta: {
    order_id: orderId,
    items: cart.map(i => i.id).join(',')
  },
  success_url: `https://store.com/orders/${orderId}/success`,
  cancel_url: `https://store.com/cart`
});

window.location.href = checkoutResponse.data.checkout_url;

Subscription Payment

tsara.pay({
  public_key: PUBLIC_KEY,
  trx_id: `sub_${customerId}_${Date.now()}`,
  amount: 500000,
  currency: 'NGN',
  email: customer.email,
  meta: {
    subscription_id: subscription.id,
    plan: 'pro',
    billing_cycle: 'monthly'
  },
  customizations: {
    title: 'Monthly Subscription',
    description: 'Pro Plan - ₦5,000/month'
  },
  onSuccess: function(response) {
    activateSubscription(subscription.id);
  }
});

Donation Page

const donationAmount = document.getElementById('amount').value;

tsara.pay({
  public_key: PUBLIC_KEY,
  trx_id: `donation_${Date.now()}`,
  amount: donationAmount * 100,
  currency: 'NGN',
  email: donor.email,
  name: donor.name,
  meta: {
    cause: 'education',
    campaign_id: 'camp_123'
  },
  customizations: {
    title: 'Support Education',
    description: 'Your donation makes a difference'
  }
});

USDC Crypto Payment

{
  "amount": 100,
  "currency": "USDC",
  "trx_id": "crypto_payment_001",
  "email": "[email protected]",
  "meta": {
    "payment_type": "crypto",
    "product": "premium_plan"
  }
}

Note: Amount is in full USDC units (100 = 100 USDC, not 0.001 USDC)


Tips & Best Practices

  1. Always verify payments server-side

    app.get('/payment-callback', async (req, res) => {
      const isValid = await verifyPayment(req.query.trx_id);
      if (isValid) {
        await fulfillOrder(req.query.trx_id);
      }
      res.redirect('/order-confirmation');
    });
  2. Use unique transaction IDs

    const trxId = `order_${orderId}_${Date.now()}_${randomString()}`;

    Prevents duplicate payments if user refreshes checkout page.

  3. Handle webhooks asynchronously

    app.post('/webhooks/tsara', (req, res) => {
      queue.add('process-payment', req.body);
      res.sendStatus(200);
    });
  4. Add metadata for tracking

    {
      "meta": {
        "order_id": "ORD-123",
        "customer_id": "cus_456",
        "source": "mobile_app",
        "campaign": "summer_sale"
      }
    }
  5. Test in sandbox thoroughly

    • Use pk_test_... public keys
    • Test all payment methods (card, bank transfer, USDC)
    • Test success, failure, and cancellation flows
  6. Implement proper error handling

    tsara.pay({
      ...paymentData,
      onError: function(error) {
        logError(error);
        showUserFriendlyMessage();
        enableRetryButton();
      }
    });
  7. Set meaningful customizations

    {
      "customizations": {
        "title": "Acme Store",
        "description": "Order #ORD-123 - 3 items",
        "logo": "https://acmestore.com/logo.png"
      }
    }
  8. Monitor session expiration

    • Sessions expire after 24 hours
    • Show countdown timer on checkout page
    • Create new session if expired
  9. Handle mobile users

    • Embedded checkout works in mobile browsers
    • Test on iOS Safari and Android Chrome
    • Ensure redirect URLs are mobile-friendly
  10. Amount formatting

    • NGN: ₦2,000.00 = 200000 (kobo)
    • USDC: 50 USDC = 50 (full units)

Troubleshooting

Checkout page not loading

Cause: Invalid session ID or expired session.

Solution: Check session expiration and create a new session:

const response = await createCheckoutSession(paymentData);
if (response.data.expires_at < new Date()) {
  console.log('Session expired, creating new one');
}

Duplicate transaction error

Cause: trx_id already used for a previous checkout.

Solution: Generate unique transaction IDs:

const trxId = `order_${orderId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;

JavaScript SDK not loading

Cause: Script blocked by ad blocker or CSP policy.

Solution:

  1. Check browser console for errors
  2. Add Tsara domains to CSP whitelist:
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' https://checkout.tsara.ng;">
  3. Test in incognito mode without extensions

Payment successful but webhook not received

Cause: Webhook URL not configured or endpoint returning errors.

Solution:

  1. Configure webhook URL in dashboard: Settings → Developers → Webhooks
  2. Ensure endpoint returns 200 OK
  3. Verify signature validation is correct
  4. Check webhook logs in dashboard

Redirect URL not working

Cause: URL not properly encoded or contains invalid characters.

Solution: Use proper URL encoding:

const redirectUrl = encodeURIComponent('https://example.com/callback?order=123&customer=456');

Amount showing incorrectly

Cause: Wrong amount format for currency.

Solution:

  • NGN: Use kobo (minor units)
    • ₦2,000.00 → 200000
    • ₦50.50 → 5050
  • USDC: Use full units
    • 50 USDC → 50
    • 0.5 USDC → 0.5

Customizations not showing

Cause: Logo URL inaccessible or wrong format.

Solution:

  1. Logo URL must be HTTPS
  2. Image must be publicly accessible
  3. Recommended: Square image, max 2MB
  4. Supported formats: PNG, JPG, SVG

Related Pages