Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/own-pay/OwnPay-Documentation/llms.txt

Use this file to discover all available pages before exploring further.

OwnPay’s REST API works equally well from Node.js server-side code and from browser-based applications. This guide covers both environments: building a typed TypeScript client for backend use, creating payment intents, verifying outcomes, handling webhooks in Express, and issuing refunds. Browser-specific considerations — such as keeping API keys out of client-side code — are addressed separately.
Never expose your API key in browser-side JavaScript. All requests to the OwnPay API that require authentication must be made from your server. Use the browser only to redirect customers to the checkout_url returned by your backend.

Node.js Setup

Store your credentials as environment variables:
export OWNPAY_API_KEY="op.xxxxxxxxxxxxx"
export OWNPAY_BASE_URL="https://pay.yourbrand.com/api/v1"
No extra packages are required — Node.js 18+ ships with the Fetch API. For older Node.js versions, install node-fetch:
npm install node-fetch   # only for Node.js < 18

TypeScript Client

// ownpay-client.ts

export interface CreatePaymentRequest {
  amount: string;
  currency: string;
  callback_url?: string;
  redirect_url?: string;
  cancel_url?: string;
  customer_email?: string;
  customer_name?: string;
  customer_phone?: string;
  reference?: string;
  gateway?: string;
  metadata?: Record<string, unknown>;
}

export interface PaymentIntent {
  payment_id: string;
  token: string;
  checkout_url: string;
  status: string;
}

export interface Transaction {
  id: number;
  trx_id: string;
  gateway_trx_id: string;
  amount: string;
  currency: string;
  fee: string;
  net_amount?: string;
  status: string;
  gateway: string;
  method: string;
  reference: string;
  created_at: string;
  completed_at: string | null;
}

export interface Refund {
  id: number;
  uuid: string;
  transaction_id: number;
  trx_id: string;
  gateway_trx_id: string;
  amount: string;
  reason: string;
  status: string;
  processed_at: string;
  created_at: string;
}

export class OwnPayError extends Error {
  constructor(
    public readonly statusCode: number,
    message: string,
    public readonly errors: Array<{ code: string; message: string; field: string }> = [],
    public readonly requestId: string = ''
  ) {
    super(`[${statusCode}] ${message} (request_id=${requestId})`);
    this.name = 'OwnPayError';
  }
}

export class OwnPayClient {
  private readonly baseUrl: string;
  private readonly apiKey: string;

  constructor(
    baseUrl = process.env.OWNPAY_BASE_URL!,
    apiKey  = process.env.OWNPAY_API_KEY!
  ) {
    this.baseUrl = baseUrl;
    this.apiKey  = apiKey;
  }

  private async request<T>(
    method: string,
    path: string,
    body?: unknown
  ): Promise<T> {
    const resp = await fetch(`${this.baseUrl}${path}`, {
      method,
      headers: {
        Authorization:  `Bearer ${this.apiKey}`,
        Accept:         'application/json',
        ...(body ? { 'Content-Type': 'application/json' } : {}),
      },
      ...(body ? { body: JSON.stringify(body) } : {}),
    });

    const json = await resp.json();

    if (!resp.ok) {
      throw new OwnPayError(
        resp.status,
        json.error ?? `HTTP ${resp.status}`,
        json.errors ?? [],
        json.request_id ?? ''
      );
    }

    return json.data as T;
  }

  async health() {
    return this.request<Record<string, unknown>>('GET', '/health');
  }

  async createPayment(req: CreatePaymentRequest): Promise<PaymentIntent> {
    return this.request<PaymentIntent>('POST', '/payments', req);
  }

  async getPayment(paymentId: string): Promise<Transaction> {
    return this.request<Transaction>('GET', `/payments/${paymentId}`);
  }

  async listTransactions(params?: {
    page?: number;
    per_page?: number;
    status?: string;
    gateway?: string;
    from?: string;
    to?: string;
  }): Promise<Transaction[]> {
    const qs = new URLSearchParams(
      Object.entries(params ?? {})
        .filter(([, v]) => v !== undefined)
        .map(([k, v]) => [k, String(v)])
    ).toString();
    return this.request<Transaction[]>('GET', `/transactions${qs ? `?${qs}` : ''}`);
  }

  async getTransaction(trxId: string): Promise<Transaction> {
    return this.request<Transaction>('GET', `/transactions/${trxId}`);
  }

  async createRefund(req: {
    trx_id: string;
    amount?: string;
    reason?: string;
  }): Promise<Refund> {
    return this.request<Refund>('POST', '/refunds', req);
  }

  async createCustomer(req: {
    name: string;
    email?: string;
    phone?: string;
  }): Promise<{ id: number; uuid: string }> {
    return this.request('POST', '/customers', req);
  }

  async getCustomer(identifier: string) {
    const encoded = encodeURIComponent(identifier);
    return this.request('GET', `/customers/${encoded}`);
  }
}

Creating a Payment Intent

import { OwnPayClient } from './ownpay-client';

const client = new OwnPayClient();

const intent = await client.createPayment({
  amount:         '500.00',
  currency:       'BDT',
  reference:      'INV-10029',
  callback_url:   'https://my-store.com/webhooks/ownpay',
  redirect_url:   'https://my-store.com/checkout/success',
  cancel_url:     'https://my-store.com/checkout/cancel',
  customer_email: 'customer@example.com',
  customer_name:  'John Doe',
  metadata:       { store_id: 'dhaka-branch' },
});

console.log('Payment ID  :', intent.payment_id);
console.log('Checkout URL:', intent.checkout_url);

// In an Express handler:
// res.redirect(intent.checkout_url);

Verifying a Payment (Express)

import express from 'express';

const app    = express();
const client = new OwnPayClient();

app.get('/checkout/return', async (req, res) => {
  const paymentId = req.session?.paymentId as string;

  if (!paymentId) {
    return res.status(400).send('No payment in session');
  }

  try {
    const payment = await client.getPayment(paymentId);

    if (payment.status !== 'completed') {
      return res.status(400).send(`Payment not completed: ${payment.status}`);
    }

    // Safe to fulfil the order
    console.log(`Order confirmed: ${payment.trx_id}`);
    return res.redirect('/order/success');
  } catch (err) {
    console.error('Payment verification failed:', err);
    return res.status(500).send('Verification error');
  }
});
Always call getPayment or getTransaction from your server to verify the outcome. Query parameters on redirect_url are user-controlled and cannot be trusted.

Handling Webhooks (Express)

import express from 'express';

const app    = express();
const client = new OwnPayClient();

app.post('/webhooks/ownpay', express.json(), async (req, res) => {
  const { event, data } = req.body;

  if (event !== 'payment.completed') {
    return res.status(400).json({ ok: false, reason: 'unrecognised event' });
  }

  try {
    // Re-verify — never act on the webhook payload alone
    const transaction = await client.getTransaction(data.trx_id);

    if (transaction.status !== 'completed') {
      return res.status(400).json({ ok: false, reason: 'not completed' });
    }

    console.log(`Fulfilling order: ${transaction.reference}`);
    // await fulfillOrder(transaction.reference);

    return res.json({ ok: true });
  } catch (err) {
    console.error('Webhook processing error:', err);
    return res.status(500).json({ ok: false });
  }
});
Acknowledge OwnPay’s webhook within 10 seconds by responding 200 immediately. For slow operations (database writes, sending emails), enqueue a background job and confirm receipt right away.

Listing Transactions

const transactions = await client.listTransactions({
  status:   'completed',
  from:     '2026-06-01',
  to:       '2026-06-30',
  per_page: 50,
});

for (const txn of transactions) {
  console.log(`${txn.trx_id}  ${txn.amount} ${txn.currency}  ${txn.status}`);
}

Issuing a Refund

const refund = await client.createRefund({
  trx_id: 'OP-481029304',
  amount: '150.00',
  reason: 'Customer requested return',
});

console.log(`Refund ${refund.uuid} — status: ${refund.status}`);

Error Handling

import { OwnPayClient, OwnPayError } from './ownpay-client';

try {
  await client.createPayment({ amount: '-1', currency: 'BDT' });
} catch (err) {
  if (err instanceof OwnPayError) {
    console.error(`API error ${err.statusCode}: ${err.message}`);
    for (const fe of err.errors) {
      console.error(`  Field '${fe.field}': ${fe.message}`);
    }
  } else {
    throw err; // Re-throw unexpected network errors
  }
}
StatusMeaningAction
400Business rule violationShow message to operator
401Invalid API keyCheck OWNPAY_API_KEY env var
403Insufficient scopeRe-generate key with correct scopes
404Resource not foundValidate IDs before requesting
409Duplicate customer emailLook up existing customer instead
422Validation failureInspect errors array for field details
503System degradedRetry with exponential back-off

Browser Integration Notes

In a browser context you should never call OwnPay API endpoints directly from the page. Instead:
  1. Your frontend calls your own backend (e.g. POST /api/create-checkout).
  2. Your backend calls POST /payments using the secret API key.
  3. Your backend returns the checkout_url to the browser.
  4. Your frontend redirects the user: window.location.href = checkoutUrl.
// Frontend JavaScript — no API key involved
async function startCheckout(orderData) {
  const resp = await fetch('/api/create-checkout', {
    method:  'POST',
    headers: { 'Content-Type': 'application/json' },
    body:    JSON.stringify(orderData),
  });

  const { checkoutUrl } = await resp.json();
  window.location.href = checkoutUrl; // Redirect to OwnPay hosted checkout
}

Build docs developers (and LLMs) love