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

# Paystack

> Paystack provider for PayKit.

```bash theme={null}
npm install @paykit-sdk/paystack
```

## Setup

<Tabs>
  <Tab title="Environment variables">
    ```typescript theme={null}
    import { PayKit } from '@paykit-sdk/core';
    import { paystack } from '@paykit-sdk/paystack';

    export const paykit = new PayKit(paystack());
    ```

    Required env vars:

    ```bash theme={null}
    PAYSTACK_SECRET_KEY=sk_test_...
    PAYSTACK_WEBHOOK_SECRET=your-webhook-secret
    ```
  </Tab>

  <Tab title="Direct config">
    ```typescript theme={null}
    import { PayKit } from '@paykit-sdk/core';
    import { createPaystack } from '@paykit-sdk/paystack';

    export const paykit = new PayKit(
      createPaystack({ secretKey: 'sk_test_...', isSandbox: true }),
    );
    ```
  </Tab>
</Tabs>

## How it works

```typescript theme={null}
const checkout = await paykit.checkouts.create({
  customer: { email: 'user@example.com' },
  item_id: 'PLN_abc123',
  session_type: 'one_time',
  quantity: 1,
  success_url: 'https://example.com/success',
  cancel_url: 'https://example.com/cancel',
  metadata: null,
  provider_metadata: {
    amount: 500000, // in kobo (NGN) or smallest currency unit
    currency: 'NGN',
  },
});

// Redirect the customer to checkout.payment_url

// Once they're back, confirm the payment
const payment = await paykit.payments.retrieve(checkout.id);
```

## Webhooks

Enable these events in your Paystack dashboard:

* `charge.success`
* `charge.failed`
* `customer.create`
* `customeridentification.success`
* `customeridentification.failed`
* `subscription.create`
* `subscription.not_renew`
* `subscription.disable`
* `invoice.create`
* `invoice.update`
* `invoice.payment_failed`
* `refund.pending`
* `refund.processed`
* `refund.failed`

Paystack signs webhooks with HMAC-SHA512 via the `x-paystack-signature` header.

```typescript theme={null}
const webhook = paykit.webhooks
  .setup({ webhookSecret: process.env.PAYSTACK_WEBHOOK_SECRET! })
  .on('payment.created', async event => {
    /* charge.success */
  })
  .on('payment.failed', async event => {
    /* charge.failed or invoice.payment_failed */
  })
  .on('subscription.created', async event => {
    /* ... */
  })
  .on('customer.created', async event => {
    /* ... */
  })
  .on('invoice.generated', async event => {
    /* ... */
  });

await webhook.handle({
  body: rawBody,
  headersAsObject: Object.fromEntries(request.headers),
  fullUrl: request.url,
});
```

| Raw event | PayKit event |
| - | - |
| `paystack.charge.success` | `payment.updated` + `invoice.generated` |
| `paystack.charge.failed` | `payment.failed` |
| `paystack.customer.create` | `customer.created` |
| `paystack.customeridentification.success/failed` | `customer.updated` |
| `paystack.subscription.create` | `subscription.created` |
| `paystack.subscription.not_renew/disable` | `subscription.canceled` |
| `paystack.invoice.create/update` | `payment.created` |
| `paystack.invoice.payment_failed` | `payment.failed` |
| `paystack.refund.pending/processed/failed` | `refund.created` |

Everything else — disputes, dedicated accounts, payment requests, transfers — is available as a raw event only.

### Raw Paystack events

Opt into any native Paystack event — fully typed against Paystack's API types:

```typescript theme={null}
paykit.webhooks
  .setup({ webhookSecret: process.env.PAYSTACK_WEBHOOK_SECRET! })
  .on('paystack.charge.success', async event => {
    // event.data is typed as PaystackTransaction
  })
  .on('paystack.subscription.create', async event => {
    // event.data is typed as PaystackSubscription
  })
  .on('paystack.refund.processed', async event => {
    // event.data is typed as PaystackRefund
  });
```

All available raw events:

| Event | Data type |
| - | - |
| `paystack.charge.success` | `PaystackTransaction` |
| `paystack.charge.dispute.create/remind/resolve` | `PaystackDispute` |
| `paystack.customeridentification.success/failed` | `PaystackCustomerIdentification` |
| `paystack.dedicatedaccount.assign.success/failed` | `PaystackDVAAssignment` |
| `paystack.invoice.create/update/payment_failed` | `PaystackInvoice` |
| `paystack.paymentrequest.pending/success` | `PaystackPaymentRequest` |
| `paystack.refund.failed/pending/processed/processing` | `PaystackRefund` |
| `paystack.subscription.create/enable/disable/not_renew` | `PaystackSubscription` |
| `paystack.transfer.success/failed/reversed` | `PaystackTransfer` |

## provider\_metadata

```typescript theme={null}
// checkout.provider_metadata is typed as { amount?: number; currency?: string }
await paykit.checkouts.create({
  customer: { email: 'user@example.com' },
  item_id: 'PLN_abc123',
  session_type: 'one_time',
  quantity: 1,
  success_url: '...',
  cancel_url: '...',
  provider_metadata: {
    amount: 500000, // override amount in kobo
    currency: 'NGN',
  },
});

// refund.provider_metadata is typed as { merchant_note?: string; customer_note?: string }
// Paystack requires a merchant_note. The SDK will prioritize provider_metadata.merchant_note,
// fall back to the universal `reason` field, or default to "Duplicate charge".
await paykit.refunds.create({
  payment_id: 'ref_abc123',
  amount: 5000,
  reason: 'Customer requested cancellation',
  metadata: null,
  provider_metadata: {
    customer_note: 'Sorry for the inconvenience',
  },
});
```

## Escape hatch

`paykit._native` returns PayKit's [HTTP client](/concepts/http-client), already pointed at Paystack and authenticated with your credentials. Use it to call endpoints PayKit does not map yet.

```typescript theme={null}
const result = await paykit._native.get(
  '/transaction/verify/ref_123',
);

if (result.ok) console.log(result.value);
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.