Accept payments
One payment object. Every rail your customers use.
Create a payment_intent, let the customer choose transfer, QR, wallet, voucher or a tap with no network, and learn the outcome from one signed webhook. USSD is coming, and cards follow once our PCI DSS assessment completes.
Card: card number, expiry and CVC fields with a Pay ₦2,500.00 button. Transfer: transfer exactly ₦2,500.00 to account 0123456789, account name Acme Stores ORD-1001, expiring in 29 minutes. USSD, coming soon: dial a code on any phone. QR: scan with your bank app or any NQR app. Wallet: enter your wallet PIN to pay ₦2,500. Voucher: voucher BRG-7Q2K-9H3M covers ₦1,500.00, leaving ₦1,000.00 to pay.
Every rail
Ten ways to pay. One status machine.
Every rail returns the same payment_intent. What changes is the next_action your page
shows: a redirect, a code to dial, an account to transfer to, a QR to scan. The outcome always arrives as
payment_intent.succeeded.
-
Coming soon: Card
Visa, Mastercard and Verve, with 3D Secure and the local step-ups: PIN, OTP, phone, birthday, address. Live once our PCI DSS assessment completes.
redirect_to_urlotppin -
Transfer
A dynamic account number for each payment, with the order in the account name. You choose what happens if the customer pays too little or too much.
display_bank_transfer -
Coming soon: USSD
A code on every intent, link and invoice. The customer dials it on any phone, including a feature phone. Coming soon.
display_ussd_code -
QR
NQR and PediWave QR, as a dynamic code on a screen or a static sticker on the counter.
display_qr -
Coming soon: Mobile money
A prompt on the customer's handset for MTN MoMo, Airtel Money and M-Pesa. They approve with their PIN.
await_mobile_money_prompt -
Wallet
Pay from a wallet with a PIN step-up that goes to the wallet, never to you. Apple Pay and Google Pay are coming.
pin -
Voucher
Take a voucher in full or in part, then the rest on any rail. Issue your own gift, promo and refund vouchers.
pin(when the voucher has one) -
Coming soon: Agent cash
The customer pays cash at an agent with a short code, and the agent's app confirms it.
await_agent_cash -
Coming soon: POS
Push a payment to a terminal at the till. The terminal checks it is online first, then takes a card, a QR or a tap.
collect_payment -
Offline tap
The customer's phone taps your terminal with no network on either side. The payment settles when the network returns.
offline_tap
Chips show the next_action type your page handles for that rail, except POS, which shows the terminal
action. Rails marked Coming are on the published roadmap and are not yet available.
Hosted Checkout and Payment Links
Share one link. Get paid from a smartphone or a feature phone.
Every Payment Link carries a web address and a QR, with a USSD code coming, so the customer pays whichever way suits their phone. The hosted page shows only the rails you have turned on and that are working right now. When you want the checkout inside your own page, use embedded components with the same rails and the same webhook.
- Single-use links for one order, or reusable links for a price list or a donation box.
- Collect the customer's email, phone, address or your own fields.
- Your name and colours on the hosted page.
Invoices by SMS and WhatsApp
Send an invoice by text. Get paid in parts, on any rail.
Create an invoice with line items and a due date, and PediWave sends it by SMS, WhatsApp or email with a payment link and an account number for transfer. Customers can pay part now and the rest later, by transfer, and by USSD or card once they go live. If the due date passes, PediWave sends the reminder and tells you.
- Partial payments recorded against the invoice, each with its own receipt.
- Reminders when an invoice becomes overdue.
- Tax lines from your tax settings.
// 1. Confirm returns the issuer's first step-up
{ "status": "requires_action",
"next_action": { "type": "pin" } }
// 2. Send the PIN token from hosted fields
{ "challenge_type": "pin",
"payload": { "pin_token": "pnt_01J9ZK…" } }
// 3. The issuer asks for an OTP next
{ "status": "requires_action",
"next_action": { "type": "otp", "otp": {
"channel": "sms", "masked_destination": "080***1234" } } }
// 4. Send the OTP
{ "challenge_type": "otp", "payload": { "otp": "123456" } }
// 5. Done
{ "status": "succeeded", "amount_received": 2500000 } Cards Coming
Verve, Visa and Mastercard, with every local step-up.
When the issuer asks for a PIN, an OTP, a phone number, a date of birth or an address, the intent returns that as
its next_action, and your page answers it on one endpoint. Card details go from hosted fields straight
to PediWave's PCI vault, so your servers never see them and you stay at the lightest PCI questionnaire, SAQ A.
- Authorise now and capture later, capture part of the amount, or void.
- Raise or lower an open authorisation where the acquirer supports it.
- Reverse in one call: PediWave voids if the payment is uncaptured and refunds if it is captured.
Network tokensComing
Read the cards guide (opens docs site)Pay with transfer
Transfers that match themselves to the order.
Each payment gets its own account number, or each customer gets a dedicated one, so every naira lands against the right order with no reference to type. Decide in advance what happens when the amount is wrong: hold an underpayment until the rest arrives, accept part, or refuse it, and refund an overpayment automatically.
- Exact, at least, a range, or any amount, for orders, top-ups and donations.
- A late transfer after the account expires is flagged for you, never lost.
payment_intent.partially_paidtells you the moment part of the money arrives.
Split tender
A voucher pays part. Another rail pays the rest.
Apply a voucher to an intent and PediWave records what it covered and what is left. The customer pays the
remainder on any rail, and you get one payment_intent.succeeded with every tender listed. If the
remaining payment fails after the voucher is applied, the voucher redemption can be reversed.
- Accept PediWave vouchers, your own gift, promo and refund vouchers, or both.
- Refund to a new voucher instead of moving money, when the customer prefers it.
- Up to three vouchers on one intent by default.
Split tender in hosted CheckoutComing
Read the vouchers guide (opens docs site)For developers
Four rails, one call.
- Idempotency-Key on every call that moves money. A retry returns the first answer, never a second payment.
- Signed webhooks with a timestamp, so you can check every delivery came from PediWave.
- A dated API version pinned to your key. New fields arrive without breaking you.
- A sandbox for every rail, with simulated transfers, USSD, offline taps and test cards.
import PediWave from "@pediwave/node";
const pediwave = new PediWave(process.env.PEDIWAVE_SECRET_KEY);
const intent = await pediwave.paymentIntents.create({
amount: 250000, // ₦2,500.00 in kobo
currency: "NGN",
reference: "ORD-1001",
payment_method_types: ["bank_transfer", "qr", "offline"],
return_url: "https://acme.example/return",
}, { idempotencyKey: "ORD-1001-1" });
// Hand intent.client_secret to your page or the mobile SDK import os
import pediwave
client = pediwave.Client(os.environ["PEDIWAVE_SECRET_KEY"])
intent = client.payment_intents.create(
amount=250000, # ₦2,500.00 in kobo
currency="NGN",
reference="ORD-1001",
payment_method_types=["bank_transfer", "qr", "offline"],
return_url="https://acme.example/return",
idempotency_key="ORD-1001-1",
) curl https://api.pediwave.com/v1/payment/intents \
-H "Authorization: Bearer $PEDIWAVE_SECRET_KEY" \
-H "Idempotency-Key: ORD-1001-1" \
-H "PediWave-Version: 2026-10-01" \
-H "Content-Type: application/json" \
-d '{
"amount": 250000,
"currency": "NGN",
"reference": "ORD-1001",
"payment_method_types": ["bank_transfer", "qr", "offline"],
"return_url": "https://acme.example/return"
}' {
"id": "pi_01J9ZK3M7Q8R1S2T3U4V5W6X7Y",
"object": "payment_intent",
"amount": 250000,
"amount_received": 0,
"currency": "NGN",
"status": "requires_payment_method",
"payment_method_types": ["bank_transfer", "qr", "offline"],
"reference": "ORD-1001",
"amount_mode": "exact",
"expires_at": "2026-10-08T11:15:30Z",
"next_action": null,
"client_secret": "pi_01J9ZK…_secret_…",
"livemode": false
} POST https://acme.example/webhooks/pediwave
PediWave-Event-Type: payment_intent.succeeded
PediWave-Timestamp: 1759918530
PediWave-Signature: t=1759918530,v1=5257a869e7…
{ "id": "evt_01J9…", "type": "payment_intent.succeeded",
"data": { "object": {
"id": "pi_01J9ZK3M7Q8R1S2T3U4V5W6X7Y",
"status": "succeeded",
"amount_received": 250000,
"latest_attempt": "att_01J9…" },
"previous_attributes": { "status": "processing" } } } How it behaves
Built for the bad days, not just the good ones.
-
A timeout is never a failure.
If a bank or acquirer does not answer, PediWave marks the payment as unknown and asks the provider what happened before doing anything else. It never tries a second provider while the first might still have taken the money.
-
Never a double debit.
Every call that moves money carries an idempotency key. If your server retries after a dropped connection, it gets the first answer back, not a second payment. A reused key with a different amount is refused.
-
Settled, not just paid.
A payment succeeds when the money is recorded on the ledger behind PediWave, with fees, splits and settlement worked out from that record. What you see in the dashboard is what will reach your account.
Questions, answered.
Something else? Talk to sales or read the docs.
Which card schemes do you support?
next_action, so your page answers each step on one endpoint. 3D Secure runs where the issuer requires it.Do I need to be PCI compliant?
How does pay with transfer match money to an order?
Can a customer pay part by voucher?
What happens when a rail is down?
Can I charge a saved card later?
How much does it cost?
Related products
Start accepting on every rail.
A test key in five minutes. Live keys after verification.