Documentation
Everything you need to accept payments with MeskelPay — from your first payout account to a full API integration.
Overview
MeskelPay is a non-custodial payment layer for Ethiopian businesses. Customers pay your own Telebirr, CBE, M-Pesa or bank account directly; MeskelPay never touches the money. What it adds on top:
- A hosted checkout page you can send customers to, with your accounts and payment QR codes.
- A unique amount tag and memo on every payment, so you can match each transfer in your bank or Telebirr statement.
- A dashboard where you confirm arrived payments in one click.
- Signed webhooks and a verify API, so your website or app reacts automatically once you confirm.
Because the money flows account-to-account between your customer and you, you don't need a payment-operator license to use MeskelPay.
How it works
- Create a payment. Your server calls
POST /api/v1/transaction/initialize(or the WooCommerce plugin does it for you). MeskelPay reserves a unique amount — e.g. 250.00 becomes 250.37 — plus a short memo code, and returns a hosted checkout URL. - The customer pays you directly. On the checkout page they pick a channel (Telebirr, CBE…), see your account number and payment QR, and transfer the exact amount. You confirm it once it arrives.
- You confirm. The payment appears in your dashboard as awaiting confirmation. Check your bank or Telebirr statement for the tagged amount or memo, then click Confirm.
- Everyone is notified. The checkout page flips to paid and redirects the customer to your
return_url; MeskelPay signs and delivers acharge.successwebhook to your endpoints.
POST /api/ingest/sms and MeskelPay will match and settle transactions automatically — no manual confirmation needed.Dashboard guide
Businesses
One account can run several businesses. Each business keeps its own payout accounts, API keys, webhooks and transactions. Switch between them with the picker at the top of the sidebar; add, rename, upload a logo, or delete them in Settings → Businesses. The business name and logo are what customers see on checkout.
Payout accounts
Add every account you want to receive money into: Telebirr, CBE, CBE Birr, M-Pesa, BOA, Awash, Dashen and more. For each account you can attach a payment QR code — screenshot the receive-money QR from your banking app and upload it; customers can then scan instead of typing the number. Disabled accounts stay saved but are hidden from checkout.
API keys
Create a key pair per integration. The MSKPUBK-… public key is safe to expose; the MSKSECK-… secret key is shown once and must stay server-side. Revoke a key any time.
Settings
Your profile (name, email, photo) and password live in Settings, alongside business management.
Hosted checkout
Every transaction gets a checkout page at /pay/<id>. It shows your business identity, the exact amount to pay (including the match tag), your payment channels with account details and QR code, and a reference field where the customer can paste their bank confirmation code. For supported channels the customer can verify that reference instantly and self-confirm; otherwise the page polls until you confirm. Either way it then redirects to your return_url. Links expire after 60 minutes.
API reference
Authenticate server-to-server calls with your secret key: Authorization: Bearer MSKSECK-…. Amounts are in Birr; the API is Chapa-compatible so most Chapa integrations can switch by changing the base URL and keys.
Initialize a transaction
POST /api/v1/transaction/initialize
{
"amount": 250,
"currency": "ETB",
"tx_ref": "order-1042", // unique per business (optional)
"email": "customer@example.com", // optional customer fields
"first_name": "Abebe",
"phone_number": "0911…",
"callback_url": "https://yoursite.com/api/meskelpay/webhook",
"return_url": "https://yoursite.com/thank-you",
"customization": { "title": "Order #1042", "description": "…" },
"meta": { "anything": "echoed back to you" }
}
→ { "status": "success",
"data": { "checkout_url": "…/pay/txn_…", "tx_ref": "order-1042" } }Verify a transaction
GET /api/v1/transaction/verify/{tx_ref}
→ { "status": "success",
"data": { "tx_ref": "order-1042", "status": "success" | "pending" | "failed",
"amount": 250, "currency": "ETB", … } }List transactions (external dashboards)
GET /api/v1/transactions?days=182&limit=100
→ { "status": "success",
"data": { "transactions": [ { "tx_ref", "status", "amount", "customer",
"paid_to", "checkout_url", … } ],
"insights": [ { "created_ms", "paid_ms", "status",
"base_santim", "channel" } ] } }List payout accounts
GET /api/v1/accounts
→ { "status": "success",
"data": { "accounts": [ { "channel", "channel_label", "account_name",
"account_number", "qr_code", … } ] } }Confirm or reject a payment
POST /api/v1/transaction/confirm/{tx_ref} // body: { "reference": "FT…" } (optional)
POST /api/v1/transaction/fail/{tx_ref}
→ { "status": "success", "data": { "tx_ref": "…", "status": "paid" | "failed" } }Confirming through the API behaves exactly like the dashboard's Confirmbutton: webhooks fire and the customer receives their receipt email. These power the WooCommerce plugin's built-in MeskelPay dashboard.
Ingest a bank SMS (optional auto-verification)
POST /api/ingest/sms
{ "text": "You have received ETB 250.37 from … Ref TX12345" }
→ { "status": "success", "matched_by": "amount", "tx_ref": "order-1042" }Webhooks
When a payment settles, MeskelPay POSTs a charge.successevent to your transaction's callback_url and every endpoint you added under Webhooks. Each request carries an x-meskelpay-signature header: the HMAC-SHA256 of the raw body using your endpoint secret. Verify it before trusting the payload:
import crypto from "node:crypto";
export async function POST(req) {
const raw = await req.text();
const expected = crypto
.createHmac("sha256", process.env.MESKELPAY_WEBHOOK_SECRET)
.update(raw)
.digest("hex");
if (req.headers.get("x-meskelpay-signature") !== expected)
return new Response("bad signature", { status: 401 });
const event = JSON.parse(raw);
if (event.event === "charge.success") {
// mark order event.tx_ref as paid
}
return new Response("ok");
}Failed deliveries retry with backoff; a daily job flushes anything still pending.
WooCommerce plugin
The MeskelPay for WooCommerce plugin adds MeskelPay as a WooCommerce payment method with no code. Install it via Plugins → Add New → Upload Plugin, then enter your secret key under WooCommerce → Settings → Payments → MeskelPay. It supports classic and block checkout, shows your accepted channels, can render a scan-to-buy QR code on every product page, and ships with a full MeskelPay dashboard inside wp-admin. Download it from the plugin page.
Security & limits
- Secret keys and passwords are stored hashed; plaintext secrets are shown exactly once.
- Webhook payloads are HMAC-SHA256 signed; sessions are signed, httpOnly cookies.
- MeskelPay never holds, routes, or refunds money. Refunds happen directly between you and your customer.
- Checkout links expire after 60 minutes; verification emails after 24 hours.
- The unique-amount tag adds at most 0.99 Birr and is included in what the customer pays you.
Questions or issues? Reach the developer at dagm.dev.