> ## Documentation Index
> Fetch the complete documentation index at: https://ownpay.org/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> OwnPay is licensed under AGPL-3.0 and is completely free - no licensing fees.
> Production docs URL: https://ownpay.org/docs - append .md to any page URL for clean markdown.
> OwnPay requires PHP 8.3+, MySQL/MariaDB, and Redis.
> MCP server available at https://ownpay.org/docs/mcp for programmatic documentation queries.
> Use root-relative links (e.g. /quickstart) for internal navigation - do NOT include /docs prefix.
> Plugin development: consult /developer/plugin-types/ pages for correct interfaces and manifests.

# Node.js SDK - TypeScript-First Payment Integration

> Integrate OwnPay into Node.js 18+ apps with the official zero-dependency TypeScript SDK featuring automatic retries and webhook verification.

The official OwnPay Node.js SDK is a zero-dependency, TypeScript-first package for integrating payments into your backend. It uses native `fetch` and `crypto` modules with built-in retries and webhook signature verification.

<CardGroup cols={2}>
  <Card title="npm" href="https://www.npmjs.com/package/ownpay-nodejs" icon="box">
    Install via npm, yarn, or pnpm.
  </Card>

  <Card title="GitHub" href="https://github.com/own-pay/ownpay-nodejs" icon="github">
    View source code, report bugs, or contribute.
  </Card>
</CardGroup>

## Prerequisites

* Node.js 18 or higher
* An OwnPay API key from Admin > Developer Hub

## Installation

<Tabs>
  <Tab title="npm">
    ```bash theme={null}
    npm install ownpay-nodejs
    ```
  </Tab>

  <Tab title="yarn">
    ```bash theme={null}
    yarn add ownpay-nodejs
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash theme={null}
    pnpm add ownpay-nodejs
    ```
  </Tab>
</Tabs>

## Initialize the client

<Tabs>
  <Tab title="ESM">
    ```typescript theme={null}
    import OwnPay from 'ownpay-nodejs';

    // Simple initialization
    const client = new OwnPay('op_your_api_key_here');

    // With configuration options
    const client = new OwnPay({
      apiKey: 'op_your_api_key_here',
      timeout: 30000,       // Request timeout in ms (default: 30000)
      maxRetries: 2,        // Max retry attempts (default: 2)
      baseUrl: 'https://api.ownpay.com',  // Custom API URL (optional)
    });
    ```
  </Tab>

  <Tab title="CommonJS">
    ```javascript theme={null}
    const OwnPay = require('ownpay-nodejs');

    const client = new OwnPay('op_your_api_key_here');
    ```
  </Tab>
</Tabs>

<Info>
  Store your API key in environment variables. Never commit it to version control.
</Info>

```typescript theme={null}
const client = new OwnPay(process.env.OWNPAY_API_KEY!);
```

## Creating payments

```typescript theme={null}
const payment = await client.payments.create({
  amount: '100.00',
  currency: 'BDT',
  customer_email: 'customer@example.com',
  customer_name: 'John Doe',
  callback_url: 'https://your-app.com/webhook',
  redirect_url: 'https://your-app.com/success',
  cancel_url: 'https://your-app.com/cancel',
  reference: 'ORDER-12345',
  metadata: {
    order_id: '12345',
    product: 'Widget',
  },
});

console.log('Payment ID:', payment.payment_id);
console.log('Checkout URL:', payment.checkout_url);
// Redirect customer to payment.checkout_url
```

### Parameters

| Parameter        | Type                      | Required | Description                                     |
| ---------------- | ------------------------- | -------- | ----------------------------------------------- |
| `amount`         | `string \| number`        | Yes      | Payment amount (positive number)                |
| `currency`       | `string`                  | Yes      | 3-letter ISO currency code (e.g., `BDT`, `USD`) |
| `callback_url`   | `string`                  | No       | Webhook notification URL                        |
| `redirect_url`   | `string`                  | No       | Success redirect URL                            |
| `cancel_url`     | `string`                  | No       | Cancel redirect URL                             |
| `customer_email` | `string`                  | No       | Customer email address                          |
| `customer_name`  | `string`                  | No       | Customer name (max 150 chars)                   |
| `customer_phone` | `string`                  | No       | Customer phone (max 30 chars)                   |
| `reference`      | `string`                  | No       | Merchant reference ID                           |
| `gateway`        | `string`                  | No       | Preferred payment gateway                       |
| `metadata`       | `Record<string, unknown>` | No       | Custom key-value metadata                       |

### Return type

```typescript theme={null}
interface PaymentIntent {
  payment_id: string;   // Unique payment UUID
  token: string;        // Payment intent token
  checkout_url: string; // URL for customer payment
  status: string;       // Current status
}
```

## Checking payment status

```typescript theme={null}
const payment = await client.payments.get('payment-uuid-here');

console.log('Status:', payment.status);
console.log('Amount:', payment.amount);
console.log('Currency:', payment.currency);

if (payment.status === 'completed') {
  console.log('Payment completed!');
}
```

## Payment flow

A typical checkout flow looks like this:

```text theme={null}
Customer clicks "Pay"
       |
       v
client.payments.create() -> get checkout_url
       |
       v
Redirect customer to OwnPay checkout
       |
       v
Customer completes or cancels payment
       |
       v
OwnPay sends webhook -> verifyWebhookSignature()
       |
       v
Your handler updates order status
```

### Complete example

```typescript theme={null}
import OwnPay from 'ownpay-nodejs';

const client = new OwnPay(process.env.OWNPAY_API_KEY!);

async function createOrder(amount: string, customerEmail: string) {
  // 1. Create customer (optional)
  const customer = await client.customers.create({
    name: 'Customer',
    email: customerEmail,
  });

  // 2. Create payment
  const payment = await client.payments.create({
    amount,
    currency: 'BDT',
    customer_email: customerEmail,
    customer_name: 'Customer',
    callback_url: 'https://your-app.com/webhook',
    redirect_url: 'https://your-app.com/success',
    cancel_url: 'https://your-app.com/cancel',
    reference: `ORDER-${Date.now()}`,
    metadata: {
      customer_id: customer.uuid,
    },
  });

  // 3. Return checkout URL for customer redirect
  return {
    payment_id: payment.payment_id,
    checkout_url: payment.checkout_url,
  };
}

async function checkPaymentStatus(paymentId: string) {
  const payment = await client.payments.get(paymentId);

  switch (payment.status) {
    case 'completed':
      return { success: true, message: 'Payment completed' };
    case 'pending':
      return { success: false, message: 'Payment pending' };
    case 'failed':
      return { success: false, message: 'Payment failed' };
    default:
      return { success: false, message: `Status: ${payment.status}` };
  }
}
```

## Webhook handling

### Verify webhook signatures

Always verify webhook signatures to ensure payloads are from OwnPay.

```typescript theme={null}
import express from 'express';
import OwnPay, { verifyWebhookSignature } from 'ownpay-nodejs';

const app = express();
const client = new OwnPay(process.env.OWNPAY_API_KEY!);

// IMPORTANT: Use raw body for webhook verification
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  try {
    const signature = req.headers['x-ownpay-signature'] as string;
    const timestamp = req.headers['x-ownpay-timestamp'] as string;

    // Verify signature
    verifyWebhookSignature({
      payload: req.body,
      signature,
      secret: process.env.OWNPAY_WEBHOOK_SECRET!,
      timestamp,
    });

    // Parse and handle event
    const event = JSON.parse(req.body.toString());

    switch (event.event) {
      case 'payment.completed':
        console.log(`Payment completed: ${event.transaction_id}`);
        // Update order status, send confirmation, etc.
        break;
      case 'payment.failed':
        console.log(`Payment failed: ${event.transaction_id}`);
        // Notify customer, retry logic, etc.
        break;
      case 'refund.completed':
        console.log(`Refund for ${event.transaction_id} completed`);
        // Update records, notify customer
        break;
    }

    res.status(200).json({ received: true });
  } catch (error) {
    console.error('Webhook error:', error);
    res.status(400).json({ error: 'Invalid webhook' });
  }
});

app.listen(3000);
```

<Warning>
  You must use the raw request body (not parsed JSON) for webhook signature verification. In Express, use `express.raw({ type: 'application/json' })` middleware.
</Warning>

### Webhook event structure

```typescript theme={null}
interface WebhookPayload {
  event: string;              // e.g., 'payment.completed'
  transaction_id: string;     // OwnPay transaction reference
  gateway_trx_id: string;     // Gateway transaction ID
  amount: string;             // Transaction amount
  currency: string;           // Currency code
  fee: string;                // Processing fee
  gateway: string;            // Gateway slug
  gateway_type: string;       // Gateway type
  status: string;             // Transaction status
  customer: {
    name: string;
    email: string;
    phone: string;
  };
  metadata: Record<string, unknown>;
  timestamp: string;          // ISO timestamp
}
```

## Transactions

```typescript theme={null}
// List with filters and pagination
const result = await client.transactions.list({
  page: 1,
  per_page: 50,
  status: 'completed',
  gateway: 'bkash',
  from: '2024-01-01',
  to: '2024-01-31',
});

console.log('Transactions:', result.items);
console.log('Total:', result.meta.total);
console.log('Pages:', result.meta.total_pages);

// Get a specific transaction
const transaction = await client.transactions.get('OP-ABC123');
```

### Transaction reporting

```typescript theme={null}
async function getTransactionReport(startDate: string, endDate: string) {
  const result = await client.transactions.list({
    from: startDate,
    to: endDate,
    per_page: 100,
  });

  const totalAmount = result.items.reduce(
    (sum, txn) => sum + parseFloat(txn.amount), 0
  );
  const totalFees = result.items.reduce(
    (sum, txn) => sum + parseFloat(txn.fee), 0
  );

  return {
    transactions: result.items,
    total: result.meta.total,
    totalAmount,
    totalFees,
    netAmount: totalAmount - totalFees,
  };
}
```

## Refunds

```typescript theme={null}
// Full refund
const refund = await client.refunds.create({
  transaction_id: 'OP-ABC123',
  reason: 'Customer request',
});

// Partial refund
const refund = await client.refunds.create({
  transaction_id: 'OP-ABC123',
  amount: '50.00',
  reason: 'Partial return',
});

// List refunds
const refunds = await client.refunds.list({ page: 1 });

// Get refund by transaction reference
const refund = await client.refunds.get('OP-ABC123');
```

## Customers

```typescript theme={null}
// Create customer
const customer = await client.customers.create({
  name: 'Jane Smith',
  email: 'jane@example.com',
  phone: '+8801712345678',
});

console.log('Customer ID:', customer.id);
console.log('Customer UUID:', customer.uuid);

// List customers with pagination
const customers = await client.customers.list({ page: 1 });

// Get customer by email or phone
const customer = await client.customers.get('jane@example.com');
const customer = await client.customers.get('+8801712345678');
```

## API key management

<Warning>
  API key operations require an API key with both `write` and `admin` scopes, and must include the `X-Super-Admin-Email` header.
</Warning>

```typescript theme={null}
// List all API keys
const keys = await client.apiKeys.list({
  headers: { 'X-Super-Admin-Email': 'admin@example.com' },
});

// Generate new key
const result = await client.apiKeys.generate(
  {
    name: 'Production Key',
    scopes: ['read', 'write'],
  },
  {
    headers: { 'X-Super-Admin-Email': 'admin@example.com' },
  }
);

// IMPORTANT: Store this key securely! It cannot be retrieved again.
console.log('New API Key:', result.key);

// Revoke key
await client.apiKeys.revoke(keyId, {
  headers: { 'X-Super-Admin-Email': 'admin@example.com' },
});
```

## Webhook utilities

```typescript theme={null}
// Send test webhook to configured endpoint
const result = await client.webhooks.test();
console.log('Status Code:', result.status_code);
console.log('Response Time:', result.response_time_ms, 'ms');

// List recent webhook deliveries
const deliveries = await client.webhooks.deliveries();
```

## Health check

```typescript theme={null}
const health = await client.health.check();

console.log('Status:', health.status);       // 'healthy' or 'degraded'
console.log('Version:', health.version);
console.log('Database:', health.db);         // 'connected' or 'error'
console.log('Gateways:', health.gateways);
console.log('Customers:', health.customers);
```

## Error handling

The SDK provides a comprehensive error hierarchy for precise error handling.

```typescript theme={null}
import OwnPay, {
  AuthenticationError,
  PermissionError,
  NotFoundError,
  ValidationError,
  RateLimitError,
  IdempotencyError,
  ApiError,
  NetworkError,
  AbortError,
} from 'ownpay-nodejs';

try {
  const payment = await client.payments.create({
    amount: '100.00',
    currency: 'BDT',
  });
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Authentication failed:', error.message);
  } else if (error instanceof ValidationError) {
    console.error('Validation failed:', error.message);
    console.error('Field errors:', error.getFieldErrors());
  } else if (error instanceof NotFoundError) {
    console.error('Not found:', error.message);
  } else if (error instanceof RateLimitError) {
    console.error(`Rate limited. Retry after ${error.retryAfter} seconds`);
  } else if (error instanceof IdempotencyError) {
    console.error('Idempotency conflict:', error.message);
  } else if (error instanceof ApiError) {
    console.error('API error:', error.message, 'Status:', error.status);
  } else if (error instanceof NetworkError) {
    console.error('Network error:', error.message);
  } else if (error instanceof AbortError) {
    console.error('Request timed out');
  }
}
```

<Accordion title="Error reference">
  | Error                 | Status | When thrown                           |
  | --------------------- | ------ | ------------------------------------- |
  | `AuthenticationError` | 401    | Invalid or revoked API key            |
  | `PermissionError`     | 403    | Insufficient scope                    |
  | `NotFoundError`       | 404    | Resource does not exist               |
  | `ValidationError`     | 422    | Invalid parameters                    |
  | `RateLimitError`      | 429    | Too many requests                     |
  | `IdempotencyError`    | 409    | Duplicate request with different body |
  | `ApiError`            | 5xx    | Server error                          |
  | `NetworkError`        | -      | Connection failure                    |
  | `AbortError`          | -      | Request timeout or cancellation       |
</Accordion>

### Validation error details

```typescript theme={null}
catch (error) {
  if (error instanceof ValidationError) {
    // Get all errors grouped by field
    const fieldErrors = error.getFieldErrors();
    // { amount: ['Must be positive'], currency: ['Required'] }

    // Get first error for a specific field
    const amountError = error.getFirstFieldError('amount');
  }
}
```

### Common error properties

All errors expose these properties:

| Property      | Type      | Description                      |
| ------------- | --------- | -------------------------------- |
| `message`     | `string`  | Human-readable error message     |
| `type`        | `string`  | Error type identifier            |
| `status`      | `number`  | HTTP status code (if applicable) |
| `requestId`   | `string`  | Request ID for debugging         |
| `rawResponse` | `unknown` | Raw API response                 |

## Request options

All resource methods accept an optional `RequestOptions` parameter:

```typescript theme={null}
const payment = await client.payments.create(
  { amount: '100.00', currency: 'BDT' },
  {
    // Idempotency key for safe retries
    idempotencyKey: 'unique-key-123',

    // Custom headers for this request
    headers: { 'X-Custom-Header': 'value' },

    // Request timeout override (ms)
    timeout: 60000,

    // AbortSignal for cancellation
    signal: AbortSignal.timeout(10000),
  }
);
```

### Idempotency

Use idempotency keys for POST requests to safely retry without creating duplicates:

```typescript theme={null}
import { randomUUID } from 'node:crypto';

const idempotencyKey = randomUUID();

const payment = await client.payments.create(
  { amount: '100.00', currency: 'BDT' },
  { idempotencyKey }
);

// Safe to retry with the same key if network error occurs
```

### Request cancellation

Use `AbortSignal` to cancel long-running requests:

```typescript theme={null}
const controller = new AbortController();

// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);

try {
  const payment = await client.payments.create(
    { amount: '100.00', currency: 'BDT' },
    { signal: controller.signal }
  );
} catch (error) {
  if (error instanceof AbortError) {
    console.log('Request was cancelled');
  }
}
```

## Retry behavior

The SDK automatically retries failed requests for transient errors:

| Status Code | Retryable | Behavior                      |
| ----------- | --------- | ----------------------------- |
| 429         | Yes       | Respects `Retry-After` header |
| 500         | Yes       | Exponential backoff           |
| 502         | Yes       | Exponential backoff           |
| 503         | Yes       | Exponential backoff           |
| 504         | Yes       | Exponential backoff           |
| 401         | No        | Immediate failure             |
| 403         | No        | Immediate failure             |
| 404         | No        | Immediate failure             |
| 422         | No        | Immediate failure             |

### Configure retries

```typescript theme={null}
const client = new OwnPay({
  apiKey: 'op_your_key',
  maxRetries: 3,  // Default: 2
  timeout: 60000, // Default: 30000ms
});
```

## Tree-shaking

Import only what you need for smaller bundle sizes:

```typescript theme={null}
// Import only the client
import OwnPay from 'ownpay-nodejs';

// Import only webhook utilities
import { verifyWebhookSignature } from 'ownpay-nodejs/webhooks';

// Import specific types
import type { Payment, Transaction } from 'ownpay-nodejs';

// Import specific errors
import { ValidationError, AuthenticationError } from 'ownpay-nodejs';
```

## Custom configuration

For self-hosted or white-label deployments:

```typescript theme={null}
const client = new OwnPay({
  apiKey: 'op_your_key',
  baseUrl: 'https://your-domain.com',
  headers: {
    'X-App-Version': '1.0.0',
    'X-Client-Id': 'your-app',
  },
});
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="Invalid API key format">
    Ensure your API key starts with `op_` and is at least 12 characters long.

    ```typescript theme={null}
    // Wrong
    const client = new OwnPay('sk_live_...');

    // Correct
    const client = new OwnPay('op_abcdef123456...');
    ```
  </Accordion>

  <Accordion title="Authentication failed">
    * Check that your API key is active and not revoked
    * Ensure you're using the correct key for your environment (test/live)
  </Accordion>

  <Accordion title="Insufficient scope">
    Your API key doesn't have the required permissions:

    * `read` scope: GET requests
    * `write` scope: POST/PUT/PATCH/DELETE requests
    * `admin` scope: API key management operations
  </Accordion>

  <Accordion title="Webhook verification failed">
    * Ensure you're using the raw request body (not parsed JSON)
    * Check that the webhook secret matches your configuration
    * Verify the timestamp is within the tolerance window
  </Accordion>
</AccordionGroup>

## API reference summary

| Resource              | Methods                            |
| --------------------- | ---------------------------------- |
| `client.payments`     | `create()`, `get()`                |
| `client.transactions` | `list()`, `get()`                  |
| `client.refunds`      | `create()`, `list()`, `get()`      |
| `client.customers`    | `create()`, `list()`, `get()`      |
| `client.apiKeys`      | `list()`, `generate()`, `revoke()` |
| `client.webhooks`     | `test()`, `deliveries()`           |
| `client.health`       | `check()`                          |


## Related topics

- [Developer Quickstart - Build Your First Payment Integration](/docs/developer/quickstart.md)
- [OwnPay API Overview - REST API for Multi-Brand Payments](/docs/api/overview.md)
- [Retrieve Payment Details](/docs/api-reference/retrieve-payment-details.md)
- [Initiate Payment Intent](/docs/api-reference/initiate-payment-intent.md)
- [WooCommerce Plugin - Accept Payments on WordPress](/docs/developer/integration/woocommerce.md)
