Skip to content

Developers

A payments API with no surprises.

REST and JSON, one secret key per mode, integer amounts, one response envelope, idempotency on every write and webhooks you can verify in a few lines.

Create a payment
curl -X POST https://api.pletopay.com/api/v1/payments \
  -H "Authorization: Bearer $PLETOPAY_SECRET_KEY" \
  -H "Idempotency-Key: create-payment-ORDER-12345" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 5000000,
    "currency": "NGN",
    "reference": "ORDER-12345",
    "customer": { "email": "customer@example.com" }
  }'

Conventions

Learn these once; every endpoint follows them.

Base URL
https://api.pletopay.com/api/v1One host for test and live; the key you send selects the mode.
Authentication
Authorization: Bearer sk_test_… | sk_live_…One secret key per business per mode. Keep it on your server.
Amounts
Integers in kobo₦50,000.00 is 5000000. No floats.
Responses
{ status, message, data[, meta] }Errors are { status: false, message, error: { type, code, param? }, request_id }.
Idempotency
Idempotency-Key: <your key>Replays return the original response for 24 hours. Required on transfers and refunds.
Identifiers
pay_… va_… cus_… re_… stl_… evt_…Prefixed, sortable ids; your own reference travels alongside.
Versioning
PletoPay-Version: 2026-09-25Dated versions, echoed on every response. Upgrade when you choose.
Rate limits
RateLimit-Limit / -Remaining / -ResetStandard headers on every response and Retry-After on 429.

The integration, end to end

From your first request to a confirmed payment, entirely in test mode.

  1. Create a payment

    POST /payments with the amount, your reference and the customer. You get a pending payment with the account to show.

  2. Show the account number

    Render the bank_transfer details yourself, redirect to hosted checkout, or open it in the Pletopay popup on your own site.

  3. Pay it in the sandbox

    In test mode, POST /sandbox/simulate-transfer. The notification runs through the same pipeline as live money.

  4. Act on the event

    Your endpoint gets payment.succeeded with a signature. Verify it, then fulfil the reference once.

Response (abridged)
{
  "status": true,
  "message": "Payment created",
  "data": {
    "id": "pay_01J8Z5X7Q9K2M4N6P8R0S2T4V6",
    "object": "payment",
    "status": "pending",
    "amount": 5000000,
    "currency": "NGN",
    "reference": "ORDER-12345",
    "bank_transfer": {
      "account_number": "9000002140",
      "account_name": "Ada Stores Ltd",
      "expires_at": "2026-09-24T18:30:00.000Z"
    }
  }
}
Simulate the transfer (test mode)
curl -X POST https://api.pletopay.com/api/v1/sandbox/simulate-transfer \
  -H "Authorization: Bearer $PLETOPAY_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "account_number": "9000002140", "amount": 5000000 }'

Webhooks you can trust

Every delivery is signed. Verify it, act on the reference, and make your handler idempotent.

  • Signed with HMAC-SHA256 over the timestamp and the raw body; reject deliveries older than 5 minutes.
  • At-least-once delivery with event ids, so handlers should be idempotent on the evt_ id.
  • Failed deliveries retry with backoff for up to six hours; an endpoint that keeps failing is paused.
  • Replay any event from the dashboard, and send a test event to a new endpoint before you rely on it.
Delivery headers
POST /webhooks/pletopay HTTP/1.1
Content-Type: application/json
X-PletoPay-Signature: t=1727200000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-PletoPay-Timestamp: 1727200000
X-PletoPay-Event-ID: evt_01J8Z5X7Q9K2M4N6P8R0S2T4WB
X-PletoPay-Event-Type: payment.succeeded
Verify the signature
import crypto from 'node:crypto';

export function verifyPletopaySignature(secret, header, rawBody, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=', 2)));
  const t = Number(parts.t);
  if (!t || !parts.v1 || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1, 'hex');
  return given.length === expected.length && crypto.timingSafeEqual(given, expected);
}

Go deeper in the documentation

Guides, the generated API reference, every webhook event and every error code, with a Try-It panel for your test key.

The API is plain REST and JSON; the samples cover cURL, Node.js and Python, and the browser popup ships as a small script. There are no server-side libraries yet.

Get a test key and make your first request.

Sign-up takes a minute and gives you a test key straight away.