Products
Solutions
Developers
Pricing
Company
Get started Sign in

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.

One intent, six rails, one webhook. Screens are from the hosted checkout.

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.

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.

A Payment Link with its web address and QR, and a USSD code coming. Sample data.

Hosted Checkout and Payment Links

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.
Read the Checkout guide (opens docs site)
An invoice sent by SMS and paid in two parts. Sample data; VAT shown at the Nigerian standard rate.

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.
Read the invoicing guide (opens docs site)
POST /v1/payment/intents/pi_01J9ZK…/authenticate
// 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 }
A Verve card that asks for a PIN, then an OTP, answered on one endpoint. Sandbox values.

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)
A customer sends ₦2,000, then ₦500. The payment is held, then paid. Sample data.

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_paid tells you the moment part of the money arrives.
Read the pay with transfer guide (opens docs site)
One purchase paid with a voucher and a transfer, recorded on one intent. Sample data.

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.
Language
POST /v1/payment/intents
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"
  }'
Show
{
  "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" } } }
Create the intent on your server, pass the client secret to your page, and fulfil the order when the signed webhook arrives. Sandbox values.

How it behaves

Built for the bad days, not just the good ones.

Questions, answered.

Something else? Talk to sales or read the docs.

Which card schemes do you support?
Card acceptance is coming: it goes live once our PCI DSS assessment completes, and you can build against it in the sandbox today. At launch: Visa, Mastercard and Verve. Local cards that ask for a PIN, an OTP, a phone number, a date of birth or an address are handled through the intent's 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?
Card payments go live once PediWave's PCI DSS assessment completes. When they do, if you use PediWave's hosted fields, the mobile SDKs or hosted Checkout, card details go straight to our PCI vault and never touch your servers, so you qualify for SAQ A, the shortest self-assessment. PediWave does not offer an API that accepts raw card numbers. See Security & compliance for how the vault is separated.
How does pay with transfer match money to an order?
Each payment gets its own account number, held for that payment until it expires, or each customer gets a dedicated account. Money that arrives in that account belongs to that payment, so there is no reference for the customer to type. You set what happens when the amount is short or over: hold, accept part, refuse, or refund the excess.
Can a customer pay part by voucher?
Yes. Apply the voucher to the intent and PediWave records the amount it covered and what remains. The customer pays the rest by transfer or another rail, and you receive one success event that lists every tender. Split tender inside hosted Checkout is coming; through the API it is available today.
What happens when a rail is down?
PediWave watches each rail separately. When one is degraded, the hosted checkout stops offering it and the customer can pay the same intent another way, such as by transfer or QR. Account numbers and codes expire after a short window, so an old one cannot be paid by mistake later.
Can I charge a saved card later?
Card on file arrives with card acceptance, once our PCI DSS assessment completes. Then you save the card with the customer present, including 3D Secure where needed, then charge it later for subscriptions or top-ups. Failed charges can be retried on a schedule. See Billing & subscriptions for the full recurring model.
How much does it cost?
Pricing is per rail and per successful payment. See Pricing for the rate on each rail, or talk to sales about an enterprise rate card.

Start accepting on every rail.

A test key in five minutes. Live keys after verification.