Documentação da API Zumbo Pay
API REST sobre HTTPS, JSON em ambos os sentidos. Autenticação por API key (Bearer) emitida no painel. Todas as chamadas devem ser feitas do seu servidor — nunca do navegador.
Plugin WooCommerce
v1.3.0Auto-updateAceite M-Pesa, e-Mola e Visa/Mastercard no seu checkout WooCommerce. Tudo começa e termina na sua loja — sem redirect externo em M-Pesa/e-Mola.
Visão geral
Datas em ISO 8601 (UTC). Valores monetários na unidade principal (ex.: 1500.50 = 1.500,50 MZN). Toda a resposta — sucesso ou erro — é JSON.
URL base
https://zumbopay.com/api/public/v1Métodos disponíveis: cobrança via M-Pesa, eMola e Visa/Mastercard (3DS) através do Checkout Hospedado; payout manual em qualquer método e payout instantâneo M-Pesa (B2C).
Autenticação
Cada pedido envia um único cabeçalho com a sua API key (prefixo zk_live_ em produção, zk_test_ em teste):
curl https://zumbopay.com/api/public/v1/wallets \
-H "Authorization: Bearer zk_live_<SUA_CHAVE>" \
-H "X-Merchant-Id: MCH_XXXXXXXXXX"Identificadores da sua conta
Merchant ID— identifica a sua conta, formatoMCH_XXXXXXXXXX. Visível no painel → Programadores.API Key—zk_live_…/zk_test_…(autentica os pedidos).wallet_id— UUID de cada carteira (M-Pesa, e-Mola, Card). Use o do método em cada cobrança.Webhook Secret— valida assinaturas HMAC-SHA256 nos webhooks.
O cabeçalho X-Merchant-Id é recomendado em todos os pedidos. Se enviado, deve coincidir com a conta da API key — caso contrário devolvemos 403 merchant_id_mismatch. Nunca expomos UUIDs internos: identifique sempre a sua conta pelo Merchant ID (MCH_…).
Cada chave tem scopes (ex.: wallets:read, payments:write, payouts:write). Crie e revogue em Painel → Programadores.
Limites & idempotência
Cada API key tem limites por endpoint. As respostas incluem cabeçalhos X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. Quando excedido devolvemos 429 com Retry-After.
POST /charges— 60/minPOST /payments— 60/min ·GET /payments— 120/min ·GET /payments/:ref— 240/minPOST /payouts— 30/minGET /wallets— 120/min
Para garantir que reentregas não duplicam cobranças, envie source_id (em /charges) ou o cabeçalho Idempotency-Key. Repetir o mesmo identificador devolve a transacção original com "duplicate": true.
Cobrança directa (STK push)
⚠️ Dois fluxos distintos — escolha o correcto
- POST /charges — STK push directo. O cliente recebe o popup do PIN no telemóvel. Sem redirecionamento. Apenas M-Pesa (84/85) e e-Mola (86/87). NÃO suporta cartão.
- POST /payments — Checkout hospedado. Devolve checkout_url para onde redireciona o cliente. Suporta M-Pesa, e-Mola E Visa/Mastercard (3DS em iframe).
Para cartão Visa/Mastercard o PIN/3DS exige o iframe MPGS — use sempre /payments. Para PIN do telemóvel sem sair do seu site, use /charges.
Dispara um pedido STK directo no telemóvel do cliente. M-Pesa (84/85) ou e-Mola (86/87) — o canal é inferido pelo prefixo do número. Scope: payments:write
wallet_id é OBRIGATÓRIO em /charges (C2B) — identifica a carteira do método escolhido (M-Pesa ou e-Mola) onde o valor será creditado. Sem wallet_id o pedido é rejeitado com 400.
POST https://zumbopay.com/api/public/v1/charges
Authorization: Bearer zk_live_<SUA_CHAVE>
X-Merchant-Id: MCH_XXXXXXXXXX
Content-Type: application/json
{
"wallet_id": "<wallet_id>", // OBRIGATÓRIO
"amount": 250,
"msisdn": "258841234567", // 84/85 → M-Pesa · 86/87 → e-Mola
"customer_name": "Maria",
"source_id": "fatura-2026-0042" // idempotência
}
→ 200 (sucesso síncrono)
{ "data": { "channel": "mpesa", "status": "success",
"code": "INS-0", "reference": "ZUMBO5K1A2Z9X" } }
→ 202 (a aguardar PIN do cliente)
{ "data": { "status": "pending", "reference": "ZUMBO..." } }
→ 402 (recusado pelo PSP)
{ "error": { "code": "psp_declined", "message": "..." } }Exemplo cURL completo (STK M-Pesa):
curl -X POST https://zumbopay.com/api/public/v1/charges \
-H "Authorization: Bearer zk_live_<SUA_CHAVE>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: fatura-2026-0042" \
-d '{
"wallet_id": "<wallet_id_mpesa>",
"amount": 250,
"msisdn": "258841234567",
"customer_name": "Maria",
"source_id": "fatura-2026-0042"
}'Exemplo cURL — Checkout hospedado (cartão + telemóvel):
curl -X POST https://zumbopay.com/api/public/v1/payments \
-H "Authorization: Bearer zk_live_<SUA_CHAVE>" \
-H "Content-Type: application/json" \
-d '{
"title": "Fatura #0042",
"amount": 250,
"currency": "MZN",
"channels": ["mpesa","emola","card"],
"wallet_id": "<wallet_id_destino>"
}'
# → 201 { "data": { "checkout_url": "https://zumbopay.com/pay/zp-...." } }
# Redirecione o cliente para checkout_url.Para cartão Visa/Mastercard NÃO use /charges — use /payments (hosted checkout) que devolve checkout_url com formulário MPGS + 3DS embutido.
Carteiras
Listar carteiras do comerciante autenticado, com saldo, código (6 dígitos), método e moeda. Scope: wallets:read
GET https://zumbopay.com/api/public/v1/wallets
Authorization: Bearer zk_live_<SUA_CHAVE>
X-Merchant-Id: MCH_XXXXXXXXXX
→ 200
{
"data": [
{
"id": "<wallet_id>",
"wallet_code": "123456",
"name": "Loja principal",
"method": "mpesa",
"currency": "MZN",
"balance": 12450.00,
"is_active": true,
"created_at": "2026-06-22T10:00:00.000Z"
}
]
}Passo a passo — configurar wallet_id por método
- No painel → Carteiras, certifique-se de que tem uma carteira activa para cada método que pretende aceitar (M-Pesa, e-Mola e/ou Visa/Mastercard).
- Chame GET /wallets uma vez e guarde o id (UUID) de cada carteira numa variável do seu sistema: WALLET_MPESA, WALLET_EMOLA, WALLET_CARD.
- Em cada chamada de cobrança, envie o wallet_id do método correspondente. Sem ele o pedido é rejeitado com 400 missing_wallet_id.
- Confirme tudo de uma vez com GET /merchant/validate (ver secção seguinte).
Exemplo — STK M-Pesa com wallet_id correcto
curl -X POST https://zumbopay.com/api/public/v1/charges \
-H "Authorization: Bearer zk_live_<SUA_CHAVE>" \
-H "X-Merchant-Id: MCH_XXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{
"wallet_id": "$WALLET_MPESA",
"amount": 50,
"msisdn": "258841234567",
"source_id": "ord-2026-0001"
}'Exemplo — STK e-Mola (prefixo 86/87)
curl -X POST https://zumbopay.com/api/public/v1/charges \
-H "Authorization: Bearer zk_live_<SUA_CHAVE>" \
-H "X-Merchant-Id: MCH_XXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{
"wallet_id": "$WALLET_EMOLA",
"amount": 50,
"msisdn": "258861234567",
"source_id": "ord-2026-0002"
}'Exemplo — Checkout hospedado (Visa/Mastercard + 3DS)
curl -X POST https://zumbopay.com/api/public/v1/payments \
-H "Authorization: Bearer zk_live_<SUA_CHAVE>" \
-H "X-Merchant-Id: MCH_XXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{
"title": "Fatura #0042",
"amount": 250,
"currency": "MZN",
"channels": ["card"],
"wallet_id": "$WALLET_CARD"
}'Validar credenciais
Endpoint de diagnóstico — chame antes de ir para produção para confirmar que tudo está pronto: Merchant ID, API Key, scopes, webhook activo e uma carteira por método.
GET https://zumbopay.com/api/public/v1/merchant/validate
Authorization: Bearer zk_live_<SUA_CHAVE>
X-Merchant-Id: MCH_XXXXXXXXXX
→ 200
{
"data": {
"ready": true,
"missing": [],
"merchant": {
"merchant_id": "MCH_XXXXXXXXXX",
"environment": "live",
"api_key": { "id": "...", "scopes": ["payments:write","wallets:read"], "environment": "live" },
"contact_phone_configured": true
},
"wallets": {
"mpesa": [{ "wallet_id": "<uuid>", "wallet_code": "553009", "name": "Loja", "currency": "MZN", "balance": 1240.5 }],
"emola": [...],
"card": [...]
},
"webhook": {
"id": "<uuid>",
"url": "https://meu-site.com/webhooks/zumbo",
"events": ["payment.succeeded","payment.failed"],
"secret": "whsec_...",
"is_active": true
},
"base_url": "https://zumbopay.com/api/public/v1"
}
}Quando ready=false, o array missing lista exactamente o que falta: merchant_id, wallet_mpesa, wallet_emola, wallet_card, webhook, contact_phone.
Links de pagamento
Crie um pedido de pagamento. A resposta inclui um checkout_url pronto para partilhar com o cliente. Scope: payments:write
POST https://zumbopay.com/api/public/v1/payments
Authorization: Bearer zk_live_<SUA_CHAVE>
X-Merchant-Id: MCH_XXXXXXXXXX
Content-Type: application/json
{
"title": "Curso de Excel",
"amount": 1500,
"currency": "MZN",
"channels": ["mpesa", "emola", "card"],
"wallet_id": "<wallet_id_obrigatório>",
"description": "Inscrição turma de Julho",
"max_uses": 1,
"expires_at": "2026-12-31T23:59:59Z"
}
→ 201
{
"data": {
"id": "<wallet_id_gerado>",
"reference": "ZP_AB12CD34",
"slug": "curso-excel",
"title": "Curso de Excel",
"amount": 1500,
"currency": "MZN",
"status": "active",
"checkout_url": "https://zumbopay.com/pay/curso-excel"
}
}channels: mpesa, emola, mkesh, card. wallet_id é obrigatório — identifica a carteira do método principal onde o valor cai. Para canais adicionais, a carteira é resolvida automaticamente entre as suas carteiras activas do método correspondente.
Listar / consultar:
GET https://zumbopay.com/api/public/v1/payments?limit=50&status=active
GET https://zumbopay.com/api/public/v1/payments/<reference-ou-slug>
Authorization: Bearer zk_live_<SUA_CHAVE>Pagamentos split (multi-beneficiário)
Distribui automaticamente cada pagamento por várias carteiras. As percentagens devem somar 100; partes fixas usam o valor absoluto em MZN.
POST https://zumbopay.com/api/public/v1/payments
Authorization: Bearer zk_live_<SUA_CHAVE>
X-Merchant-Id: MCH_XXXXXXXXXX
Content-Type: application/json
{
"type": "split",
"title": "Aula em parceria",
"amount": 1000,
"currency": "MZN",
"channels": ["mpesa", "emola", "card"],
"wallet_id": "<wallet_id_principal>",
"splits": [
{ "recipient_wallet_id": "<wallet_id_A>", "share_type": "percent", "share_value": 70 },
{ "recipient_wallet_id": "<wallet_id_B>", "share_type": "percent", "share_value": 30 }
]
}
→ 201 { "data": { "id": "<id>", "reference": "ZP-SPL-...",
"checkout_url": "https://zumbopay.com/pay/zp-spl-..." } }Máx. 20 beneficiários por link. A comissão de 8% é aplicada antes do split.
Pagamentos recorrentes
Cria uma subscrição cobrada automaticamente pelo runner diário (03:05 UTC). Para cartões usa o token guardado após a primeira autorização 3DS; para M-Pesa/e-Mola dispara STK no número fornecido.
POST https://zumbopay.com/api/public/v1/payments
Authorization: Bearer zk_live_<SUA_CHAVE>
X-Merchant-Id: MCH_XXXXXXXXXX
Content-Type: application/json
{
"type": "recurring",
"title": "Assinatura Premium",
"amount": 499,
"currency": "MZN",
"channels": ["card"],
"recurring": {
"channel": "card", // card | mpesa | emola
"interval": "monthly", // daily|weekly|monthly|quarterly|semiannual|yearly
"max_charges": 12, // opcional — nº máximo de ciclos
"start_at": "2026-07-01T00:00:00Z", // opcional — 1ª cobrança
"end_at": "2027-07-01T00:00:00Z", // opcional — data limite do mandato
"customer_name": "Cliente Premium",
"customer_email": "cliente@exemplo.mz", // alertas de cobrança
"customer_msisdn":"258841234567",
"consent_accepted": true // true = activa de imediato (integração API com consentimento auditável do lado do integrador)
}
}
→ 201 {
"data": {
"subscription_id": "<uuid>",
"status": "active", // "pending" se consent_accepted=false
"checkout_url": "https://zumbopay.com/pay/zp-rec-..."
}
}Se omitir consent_accepted, a subscrição fica pending até o cliente abrir o checkout, preencher nome/email/contacto e aceitar o mandato explicitamente. Cada cobrança bem-sucedida dispara subscription.charged. Após 3 falhas seguidas → paused. Cancele a qualquer momento via DELETE /api/public/v1/subscriptions/<id> ou no painel; o evento subscription.cancelled é emitido.
Checkout hospedado (whitelabel)
Redireccione o cliente para o URL devolvido em checkout_url:
https://zumbopay.com/pay/<reference-ou-slug>O cliente escolhe M-Pesa, eMola ou cartão (Visa/Mastercard com 3-D Secure 2). A carteira é creditada com o valor líquido (8% de comissão da plataforma). Comissão e estado são publicados via webhook em tempo real.
Payouts
Saque a partir de uma carteira. Os fundos entram sempre na conta master da Zumbo Pay; a redistribuição depende do método e do modo (auto B2C ou manual). Scope: payouts:write
e-Mola — herda das configurações da carteira
- Activar B2C automático e número de destino em Painel → Carteiras → e-Mola.
- Auto B2C ON: 100% entra no master, 88% enviado por B2C automático para o número da carteira. 12% (8% plataforma + 4% B2C) retidos. Carteira não acumula saldo.
- Auto B2C OFF: 92% creditado na carteira (8% plataforma retida). Saque manual mín. 500 MT, custo 20 MT por cada 500 MT (4%).
- Payout instantâneo M-Pesa B2C: mín. 1 MT, taxa 4%.
- Mudança do número de payout requer OTP por SMS no número antigo (anti-fraude).
M-Pesa — payout instantâneo B2C
Taxa 4% sobre o valor líquido. Sujeito ao switch global em Admin → Integrações.
Exemplo 1 — Saque manual (qualquer método)
Mínimo 500 MT. Taxa 20 MT por cada 500 MT solicitados (≈ 4%). O payout fica em status 'pending' até aprovação do admin; fee_amount e net_amount já vêm calculados na resposta.
curl -X POST https://zumbopay.com/api/public/v1/payouts \
-H "Authorization: Bearer zk_live_<SUA_CHAVE>" \
-H "X-Merchant-Id: MCH_XXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{
"wallet_id": "<wallet_id_emola>",
"amount": 1500,
"method": "emola",
"destination": "861234567",
"notes": "Saque semanal"
}'
→ 201
{
"data": {
"id": "<payout_id>",
"amount": 1500,
"fee_amount": 60,
"net_amount": 1440,
"currency": "MZN",
"status": "pending",
"method": "emola",
"destination": "861234567",
"requested_at": "2026-06-30T10:00:00.000Z",
"completes_at": null,
"auto_dispatched": false
}
}Exemplo 2 — Payout instantâneo M-Pesa B2C
Exclusivo M-Pesa. Mínimo 1 MT, taxa 4% sobre o líquido. Envia auto_dispatch=true; o B2C é disparado em tempo real e a resposta já vem com status='success' e provider_reference da operadora.
curl -X POST https://zumbopay.com/api/public/v1/payouts \
-H "Authorization: Bearer zk_live_<SUA_CHAVE>" \
-H "X-Merchant-Id: MCH_XXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{
"wallet_id": "<wallet_id_mpesa>",
"amount": 250,
"method": "mpesa",
"destination": "841234567",
"auto_dispatch": true
}'
→ 201
{
"data": {
"id": "<payout_id>",
"status": "success",
"method": "mpesa",
"destination": "258841234567",
"amount": 250,
"fee_amount": 10,
"net_amount": 240,
"currency": "MZN",
"reference": "ZP<id16>",
"provider_reference": "AG_...",
"auto_dispatched": true
}
}Pré-requisitos: KYC aprovado e saldo suficiente. Erros típicos: insufficient_funds, wallet_not_found, manual_min_amount (abaixo de 500 MT), auto_dispatch_unsupported (auto_dispatch só funciona com method=mpesa), b2c_failed (operadora rejeitou o B2C).
Webhooks
Configure URL e eventos em Painel → Programadores. Cada entrega inclui o cabeçalho x-zumbopay-signature com o HMAC-SHA256 do corpo bruto assinado com o seu webhook secret.
// Node.js — verificar assinatura
import crypto from "crypto";
function verify(rawBody, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature, "hex"),
Buffer.from(expected, "hex"),
);
}Eventos suportados: payment.succeeded, payment.failed, payment.refunded, payout.completed, payout.failed, kyc.status_changed, subscription.created, subscription.mandate_accepted, subscription.activated, subscription.charged, subscription.paused, subscription.resumed, subscription.cancelled, subscription.ended, subscription.failed.
Endpoint de callback M-Pesa (lado Zumbo Pay)
POST https://zumbopay.com/api/public/mpesa-callbackConfigurado nas nossas chaves Vodacom. O comerciante não precisa de o registar.
Códigos de erro
Formato uniforme:
{
"error": {
"code": "invalid_input",
"message": "wallet_id é obrigatório"
}
}400—invalid_json,invalid_input,insufficient_funds401—invalid_api_key,revoked_api_key,expired_api_key403—insufficient_scope404—not_found,wallet_not_found429— limite de taxa excedido5xx— erro interno; repetir com backoff exponencial
Changelog
2026-06-24
POST /v1/payments(type=recurring): novos camposend_at,consent_accepted,customer_name,customer_email,customer_msisdn.- Novos eventos de webhook:
subscription.mandate_accepted,subscription.activated,subscription.ended. DELETE /v1/subscriptions/<id>— cancelamento programático com emissão desubscription.cancelled.
2026-06-22
- Referências passam a usar prefixo
ZP…(sanitizadas; evita rejeição INS-19 do M-Pesa). /v1/charges+/v1/payments: CORS preflight (OPTIONS) suportado.- Job de reconciliação
/api/public/hooks/reconcile-api-pendingcorre a cada 2 minutos. wallet_idcontinua obrigatório em /charges (UUID) e /payments (UUID ou wallet_code de 6 dígitos). Canais adicionais em /payments resolvem automaticamente a carteira do método correspondente.
Suporte
Escreva-nos para suporte@zumbopay.com e respondemos em 24h úteis.

