Quickstart
Seven steps against a test key. None of it needs your business verified.
#1. Get a key
Dashboard → Developers. Keys are created the first time you open it.
You get four, and the mode is in the string:
sk_test_…— secret, server to server. This is the one the rest of this page uses.pk_test_…— public, safe in a web page. Names your merchant when a checkout is opened from a browser and can do nothing else.sk_live_…andpk_live_…— the same two, once your business is verified.
#2. Check the host
Prove you have the right server first. No key needed:
curl https://api.paymonetra.com/v1/If it names the Paymonetra Gateway API, your host is right and any later 401 is about your key.
#3. Issue a customer account
customer_reference is your id for the customer. We hand it back on every payment.
curl https://api.paymonetra.com/v1/customer_accounts \
-H "Authorization: Bearer sk_test_..." \
-d customer_reference=cus_9f21 \
-d customer_name="Ngozi Nwosu"{
"customer_reference": "cus_9f21",
"customer_name": "Ngozi Nwosu",
"account_number": "8021234567",
"account_name": "MONETRA - Ngozi Nwosu",
"bank_name": "Payrep MFB",
"status": "active",
"mode": "live",
"created_at": "2026-08-28T19:18:33+01:00"
}201 the first time, 200 if it already exists, same body either way. Store account_number against your user and show it to them.
#4. Start a payment
For a one-off, open a checkout and send the payer to the URL that comes back.
curl https://api.paymonetra.com/v1/payments \
-H "Authorization: Bearer sk_test_..." \
-d amount=45000 \
-d reference=SF-10482 \
-d customer_reference=cus_9f52 \
-d customer_name="Ngozi Nwosu" \
-d description="Ankara bundle"{
"reference": "ef8e7ff7078b893d03",
"merchant_reference": "SF-10482",
"status": "pending",
"amount": 45000.70,
"amount_requested": 45000,
"account_number": "8021234701",
"account_name": "MONETRA - Ngozi Nwosu",
"checkout_url": "https://pay.paymonetra.com/ef8e7ff7078b893d03",
"collects": ["bank_transfer"]
}#5. Hear about it
Set your URL under Developers and copy the signing secret next to it. When money lands we post:
POST <your url>
Content-Type: application/json
X-Paymonetra-Signature: <hex HMAC-SHA512 of the raw body, keyed with your secret>
X-Paymonetra-Event: collection.success
X-Paymonetra-Delivery: 41{
"event": "collection.success",
"mode": "live",
"created_at": "2026-08-28T18:02:11+01:00",
"data": {
"reference": "20260828140900236399951256",
"amount": 12500,
"fee": 175,
"net": 12325,
"currency": "NGN",
"rail": "bank",
"customer_reference": "cus_9f21",
"customer_name": "Ngozi Nwosu",
"account_number": "8029607081",
"payer_name": "Chidera Okeke",
"paid_at": "2026-08-28T18:02:11+01:00"
}
}Verify the signature over the raw bytes before trusting the body:
$secret = getenv('PAYMONETRA_WEBHOOK_SECRET');
$raw = file_get_contents('php://input');
$sent = $_SERVER['HTTP_X_PAYMONETRA_SIGNATURE'] ?? '';
$expected = hash_hmac('sha512', $raw, $secret);
// Compare in constant time, and never decode the body first.
if (!hash_equals($expected, $sent)) {
http_response_code(401);
exit;
}
$event = json_decode($raw, true);
// $event['data']['customer_reference'] is your own id for the customer.
http_response_code(200);const crypto = require('crypto')
// express.raw keeps the bytes. express.json would re-encode them and
// every signature would fail.
app.post('/paymonetra', express.raw({ type: 'application/json' }), (req, res) => {
const sent = req.get('X-Paymonetra-Signature') || ''
const expected = crypto
.createHmac('sha512', process.env.PAYMONETRA_WEBHOOK_SECRET)
.update(req.body)
.digest('hex')
const a = Buffer.from(sent)
const b = Buffer.from(expected)
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(401)
const event = JSON.parse(req.body.toString())
// event.data.customer_reference is your own id for the customer.
res.sendStatus(200)
})import hmac, hashlib, os
from flask import request, abort
@app.post('/paymonetra')
def paymonetra():
raw = request.get_data() # bytes, not request.json
sent = request.headers.get('X-Paymonetra-Signature', '')
expected = hmac.new(
os.environ['PAYMONETRA_WEBHOOK_SECRET'].encode(),
raw,
hashlib.sha512,
).hexdigest()
if not hmac.compare_digest(expected, sent):
abort(401)
event = request.get_json()
# event['data']['customer_reference'] is your own id for the customer.
return '', 200Answer any 2xx. Webhooks has the retry schedule.
#6. Make one arrive
A test account cannot receive a real transfer, so this stands in for one. It runs the same credit path live payments run and posts a real collection.success to your test endpoint.
curl https://api.paymonetra.com/v1/customer_accounts/cus_9f21/simulate_payment \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{"outcome":"paid","amount":20000,"payer_name":"Ada Obi"}'{
"outcome": "paid",
"credited": true,
"reference": "SIM20260830161204A3F19C22",
"amount": 20000,
"account_number": "9913607261",
"message": "Simulated paid of 20,000.00. A collection.success webhook has been queued."
}#7. Go live
- Get your business verified in the dashboard. Live keys appear when it is approved.
- Swap
sk_test_forsk_live_on your server. Nothing else in your code changes. - Set your live webhook URL as well: the endpoint and its secret are separate per mode, so a test integration can never post into a live system.
- Check pricing and the error reference before you take real money.