npm install @paykit-sdk/monnify
Setup
Environment variables
Direct config
import { PayKit } from '@paykit-sdk/core';
import { monnify } from '@paykit-sdk/monnify';
export const paykit = new PayKit(monnify());
Required env vars:MONNIFY_API_KEY=MK_TEST_...
MONNIFY_SECRET_KEY=your-secret-key
MONNIFY_SANDBOX=true
import { PayKit } from '@paykit-sdk/core';
import { createMonnify } from '@paykit-sdk/monnify';
export const paykit = new PayKit(
createMonnify({
apiKey: 'MK_TEST_...',
secretKey: 'your-secret-key',
isSandbox: true,
}),
);
How it works
Monnify uses OAuth 2.0 client credentials (API key + secret key) to obtain a short-lived access token before every API call. createCheckout calls POST /v1/merchant/transactions/init-transaction and returns a checkoutUrl. After payment, retrieve the transaction status with retrievePayment(transactionReference). Amounts are in the standard currency unit (e.g. Naira for NGN).
Monnify does not support customer management, subscriptions, or direct
payment creation. Use createCheckout and retrievePayment for the
core payment flow.
Webhooks
Monnify signs webhook payloads with HMAC-SHA512 via the monnify-signature header. Add a webhook endpoint in your Monnify dashboard that points to your handler.
const webhook = paykit.webhooks
.setup({ webhookSecret: process.env.MONNIFY_WEBHOOK_SECRET! })
.on('payment.created', async event => {
/* SUCCESSFUL_TRANSACTION */
})
.on('payment.failed', async event => {
/* REJECTED_PAYMENT */
})
.on('payment.updated', async event => {
/* SETTLEMENT */
})
.on('refund.created', async event => {
/* refund succeeded or failed */
});
await webhook.handle({
body: rawBody,
headersAsObject: Object.fromEntries(request.headers),
fullUrl: request.url,
});
Raw Monnify events
Opt into any native Monnify event type — typed against Monnify’s API payload:
paykit.webhooks
.setup({ webhookSecret: process.env.MONNIFY_WEBHOOK_SECRET! })
.on('monnify.SUCCESSFUL_TRANSACTION', async event => {
// event.data is the raw Monnify transaction payload
})
.on('monnify.MANDATE_UPDATE', async event => {
// event.data is the raw Monnify mandate payload
});
All available raw events and their PayKit mappings:
| Raw event | PayKit event |
|---|
monnify.SUCCESSFUL_TRANSACTION | payment.created |
monnify.SUCCESSFUL_TRANSACTION_OFFLINE | payment.created |
monnify.REJECTED_PAYMENT | payment.failed |
monnify.SETTLEMENT | payment.updated |
monnify.SUCCESSFUL_REFUND | refund.created |
monnify.FAILED_REFUND | refund.created |
monnify.MANDATE_UPDATE | subscription.created / subscription.canceled / subscription.updated |
monnify.CUSTOMER_CREATED | (ignored) |
monnify.CUSTOMER_UPDATED | (ignored) |
monnify.CUSTOMER_DELETED | (ignored) |
monnify.SUCCESSFUL_DISBURSEMENT | (ignored) |
monnify.FAILED_DISBURSEMENT | (ignored) |
monnify.REVERSED_DISBURSEMENT | (ignored) |
amount and currency are required in provider_metadata for checkout because Monnify does not infer them from the plan or item:
// checkout.provider_metadata requires amount and currency
await paykit.checkouts.create({
customer: { email: 'user@example.com' },
item_id: 'my-product',
session_type: 'one_time',
quantity: 1,
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/cancel',
provider_metadata: {
amount: 5000, // in the standard currency unit (e.g. Naira)
currency: 'NGN',
},
});
// refund.provider_metadata — any extra fields are forwarded to Monnify's refund endpoint
await paykit.refunds.create({
payment_id: 'MNFY|...',
amount: 1000,
reason: 'Customer request',
});