sPay Logo

Documentação Técnica

Documentação Técnica

Gateway de pagamento Angolano por validação de comprovativos

Simples de integrar

3 endpoints principais. Criar, consultar e receber webhook.

Anti-Fraude nativo

HMAC-SHA256, validação IBAN stricta, deduplicação de transações.

AI-Ready

Schema JSON de tools disponível em GET /api/agent-tools.

Base URL: https://backend-phi-seven-54.vercel.app|Content-Type: application/json

Autenticação

Endpoints protegidos requerem o header X-API-Key. Obtenha a sua chave em Configurações → API & Webhooks. Apenas o endpoint de consulta de pagamento (GET /api/payments/{id}) é público.

auth.sh
123456
# Todos os requests autenticados necessitam deste header:X-API-Key: spk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Exemplo cURL:curl "https://backend-phi-seven-54.vercel.app/api/payments" \  -H "X-API-Key: spk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Segurança: Nunca exponha a X-API-Key no código de frontend (browser). Todas as cobranças devem ser criadas pelo seu servidor backend.

Fluxo de Pagamento

O sPay valida comprovativos PDF de transferências bancárias enviados por email. O fluxo completo envolve o seu servidor, o cliente e o sPay.

1

Criar Cobrança

O seu servidor chama POST /api/payments com o montante. Recebe payment_id e pay_url.

2

Redirecionar Cliente

Envie o cliente para pay_url. A página hosted mostra o email temporário e os dados de transferência.

3

Cliente Efectua Transferência

O cliente transfere via BAI Directo, EMIS/Multicaixa Express e envia o PDF comprovativo para o email mostrado.

4

sPay Valida o PDF

O sPay recebe o email, extrai o PDF, valida IBAN/telefone, montante e unicidade da transação.

5

Webhook Disparado

Se válido, o sPay envia POST ao seu webhook com event: 'payment.paid' assinado com HMAC-SHA256.

6

Entregar Produto/Serviço

O seu servidor verifica a assinatura e activa o produto ou serviço para o cliente.

SDKs Oficiais

Integre o sPay rapidamente usando as nossas bibliotecas oficiais para Python e JavaScript/TypeScript. Ambas estão configuradas por padrão para comunicar com o ambiente de produção.

Python

SDK para Python

Biblioteca oficial para integrações em ambientes Django, Flask, FastAPI ou scripts locais.

Instalação

install_python.sh
1
pip install ./sdk/python

Uso Básico

PYexample.py
1234567891011
from spay_sdk import SPayClient # Inicializa o cliente (URL de produção por defeito)client = SPayClient(api_key="spay_live_xxxxxx") # Criar cobrançapayment = client.create_payment(amount=3000, description="Compra #1")print(payment["pay_url"]) # Listar pagamentos pagospayments = client.list_payments(status="paid", limit=10)
JS / TS

SDK para JS / TS

Biblioteca compilada e tipada para projetos Node.js, Express, NestJS e frameworks modernos.

Instalação & Build

install_node.sh
1
cd sdk/javascript && npm install && npm run build

Uso Básico

JSexample.ts
123456789101112
import { SPayClient } from './sdk/javascript'; const client = new SPayClient('spay_live_xxxxxx'); async function main() {  // Criar cobrança  const payment = await client.createPayment(3000, { description: "Compra #1" });  console.log(payment.pay_url);   // Listar pagamentos  const payments = await client.listPayments("paid", 10);}

Criar Cobrança

POST/api/payments

Cria uma nova cobrança e retorna um email temporário único para onde o cliente deve enviar o comprovativo PDF. Requer autenticação via X-API-Key.

Parâmetros do Body

CampoTipoReq.Descrição
amountnumberMontante em Kz (Kwanzas). Deve ser maior que 0.
expires_in_minutesintegerTempo de expiração em minutos. Intervalo aceite: 5 a 1440 (24h).
descriptionstringDescrição opcional da encomenda, exibida na página de checkout.
JScheckout.js
1234567891011121314151617181920212223
const createPayment = async () => {  const response = await fetch("https://backend-phi-seven-54.vercel.app/api/payments", {    method: "POST",    headers: {      "Content-Type": "application/json",      "X-API-Key": "spk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"    },    body: JSON.stringify({      amount: 3000,      expires_in_minutes: 15,      description: "Compra #1234 - Produto XYZ"    })  });   const payment = await response.json();  // Redirecionar cliente para o checkout  window.location.href = payment.pay_url;   // Ou guardar o ID para rastrear  console.log("Payment ID:", payment.payment_id);  // payment.email = "pay-pay-x8f92k@mailtm.com"  // payment.expires_at = "2026-06-16T21:15:00Z"};

Resposta 201 Created

JSONresponse.json
123456789
{  "payment_id": "pay_x8f92k",  "email": "pay-pay-x8f92k@domain.mailtm.com",  "pay_url": "https://spayment.vercel.app/pay/pay_x8f92k",  "expires_at": "2026-06-16T21:15:00.000Z",  "amount": 3000,  "currency": "Kz",  "status": "pending"}

Campos da Resposta

CampoTipoReq.Descrição
payment_idstringIdentificador único. Guarde para rastrear o pagamento.
emailstringEmail temporário. O cliente envia o PDF comprovativo para este endereço.
pay_urlstringURL pública da página de checkout hospedada pelo sPay. Redirecione o cliente aqui.
expires_atstring (ISO 8601)Data e hora de expiração da cobrança.
status"pending"Estado inicial da cobrança.

Consultar Pagamento

GET/api/payments/{payment_id}

Consulta o estado actual de uma cobrança. Não requer autenticação — pode ser chamado directamente do frontend para mostrar o estado ao cliente.

JSstatus.js
1234567891011121314
const checkStatus = async (paymentId) => {  // Não requer autenticação  const response = await fetch(    `https://backend-phi-seven-54.vercel.app/api/payments/${paymentId}`  );  const details = await response.json();   // status: "pending" | "paid" | "expired"  console.log("Estado:", details.status);  if (details.status === "paid") {    console.log("Transação:", details.transaction_id);    console.log("Pago em:", details.paid_at);  }};

Resposta 200 OK

JSONresponse.json
1234567891011
{  "payment_id": "pay_x8f92k",  "status": "paid",  "amount": 3000.0,  "currency": "Kz",  "description": "Compra #1234",  "created_at": "2026-06-16T21:00:00",  "expires_at": "2026-06-16T21:15:00",  "paid_at": "2026-06-16T21:08:42",  "transaction_id": "10963242"}
StatusDescrição
pendingAguarda o envio e validação do comprovativo.
paidComprovativo validado com sucesso. Pagamento confirmado.
expiredO tempo limite foi ultrapassado sem confirmação.

Listar Pagamentos

GET/api/payments

Lista todos os pagamentos do comerciante autenticado. Suporta filtragem por estado e paginação.

Query Parameters

CampoTipoReq.Descrição
statusstringFiltrar por estado: "pending", "paid" ou "expired".
limitintegerMáximo de resultados a retornar. Default: 50.
offsetintegerOffset de paginação. Default: 0.
JSlist_payments.js
12345678910111213
const listPayments = async () => {  const params = new URLSearchParams({    status: "paid",    limit: "20",    offset: "0"  });  const response = await fetch(    `https://backend-phi-seven-54.vercel.app/api/payments?${params}`,    { headers: { "X-API-Key": "spk_xxxxxxxxxxxxxxxxxxxxxxxx" } }  );  const payments = await response.json();  console.log(`Total recebidos: ${payments.length}`);};

Configurar Webhook

Configure a URL do seu servidor para receber notificações em tempo real quando um pagamento é confirmado. Esta é a forma preferida e mais fiável de rastrear pagamentos, em vez de polling.

POST/api/webhook/config

Configura ou actualiza a URL do webhook.

config.sh
1234
curl -X POST "https://backend-phi-seven-54.vercel.app/api/webhook/config" \  -H "Authorization: Bearer SEU_JWT_TOKEN" \  -H "Content-Type: application/json" \  -d '{"url": "https://seu-servidor.com/webhooks/spay"}'
POST/api/webhook/test

Envia um evento de teste para a URL configurada.

test.sh
12
curl -X POST "https://backend-phi-seven-54.vercel.app/api/webhook/test" \  -H "Authorization: Bearer SEU_JWT_TOKEN"

Verificação de Assinatura HMAC-SHA256

JSwebhook.js
12345678910111213141516171819202122232425262728293031323334
const crypto = require("crypto"); // No seu servidor (ex: Express)app.post("/webhooks/spay", (req, res) => {  const signature = req.headers["x-spay-signature"];  const secret = "whsec_xxxxxxxxxxxxxxxxxxxxxxxx";   // 1. Serializar o payload com chaves ordenadas  const payloadStr = JSON.stringify(    req.body,    Object.keys(req.body).sort()  );   // 2. Calcular HMAC-SHA256  const computed = crypto    .createHmac("sha256", secret)    .update(payloadStr)    .digest("hex");   // 3. Comparar de forma segura  if (computed !== signature) {    return res.status(401).send("Assinatura inválida");  }   // 4. Processar o evento  const { event, data } = req.body;  if (event === "payment.paid") {    console.log(`Pagamento ${data.payment_id} confirmado!`);    console.log(`Valor: ${data.amount} Kz`);    // → Activar o produto/serviço para o cliente  }   res.status(200).send("OK");});

Eventos de Webhook

O sPay envia um HTTP POST para o seu endpoint configurado quando ocorre um evento relevante.

EVENTpayment.paid— Disparado quando o comprovativo é validado com sucesso
JSONwebhook_payload.json
123456789101112131415
// Header recebido no seu servidor:// X-SPay-Signature: e3b0c44298fc1c149afbf4c... {  "event": "payment.paid",  "data": {    "payment_id": "pay_x8f92k",    "status": "paid",    "amount": 3000.0,    "currency": "Kz",    "transaction_id": "10963242",    "recipient_phone": "922599463",    "paid_at": "2026-06-07T12:04:05"  }}

Fontes de Pagamento Suportadas

FonteRemetente do EmailMétodo de Validação
BAI Directo (via telefone)baidirecto@bancobai.aoÚltimos 9 dígitos do telemóvel
BAI Directo (via IBAN)baidirecto@bancobai.aoIBAN completo — correspondência exacta
EMIS / Multicaixa Expressnoreply@emis.co.aoTelemóvel (últimos 9 dígitos) ou IBAN completo

Comprovativos BAI Directo de remetentes diferentes de baidirecto@bancobai.ao e comprovativos EMIS / Multicaixa Express de remetentes diferentes de noreply@emis.co.ao são rejeitados como fraudulentos.

Referência de Erros

Todas as respostas de erro seguem o formato: {"detail": "Descrição do erro em Português."}

Código HTTPQuando ocorreAcção recomendada
400Bad RequestBody malformado ou campos inválidos.Verificar a estrutura do JSON enviado.
401UnauthorizedX-API-Key ausente ou inválida.Confirmar a chave em Configurações → API & Webhooks.
403ForbiddenSaldo de faturação insuficiente ou limite do plano atingido.Recarregar o saldo em Faturação & Planos.
404Not Foundpayment_id não encontrado.Verificar se o ID está correcto.
409ConflictTransação duplicada — o mesmo PDF já foi usado.Solicitar um novo comprovativo ao cliente.
422Validation Erroramount ≤ 0 ou expires_in_minutes fora do intervalo 5–1440.Corrigir os valores dos parâmetros.
500Internal ErrorErro interno do servidor.Tentar novamente. Contactar suporte se persistir.

Limites & Planos

O sPay opera num modelo pré-pago. Cada transação confirmada debita 25 Kz do saldo de faturação. O plano define também o número máximo de contas bancárias de receção.

PlanoPreçoValidaçõesContas BancáriasCusto por Transação
Starter1.000 Kz40 transações1 conta25 Kz / transação
Boost5.000 Kz210 transações2 contas25 Kz / transação
Growth10.000 Kz450 transações4 contas25 Kz / transação
Scale25.000 Kz1.200 transações10 contas25 Kz / transação

Guia para Agentes de IA

O sPay foi desenhado para ser facilmente integrado por agentes de IA (LLMs, AutoGPT, CrewAI, LangChain, etc.). O endpoint GET /api/agent-tools retorna o schema JSON de ferramentas no formato compatível com OpenAI Function Calling.

Schema JSON de Ferramentas

JSONagent_tools.json
123456789101112131415161718192021222324252627282930313233
// GET https://backend-phi-seven-54.vercel.app/api/agent-tools// Retorna o schema JSON completo para uso em AI Agents {  "tools": [    {      "name": "create_payment",      "description": "Cria uma cobrança sPay. Retorna payment_id e pay_url.",      "parameters": {        "type": "object",        "properties": {          "amount":             { "type": "number" },          "expires_in_minutes": { "type": "integer", "default": 15 },          "description":        { "type": "string" }        },        "required": ["amount", "expires_in_minutes"]      },      "http": { "method": "POST", "path": "/api/payments" }    },    {      "name": "get_payment_status",      "description": "Consulta o status de um pagamento.",      "parameters": {        "type": "object",        "properties": {          "payment_id": { "type": "string" }        },        "required": ["payment_id"]      },      "http": { "method": "GET", "path": "/api/payments/{payment_id}" }    }  ]}

Árvore de Decisão para Agentes

agent_flow.md
1234567891011121314151617181920
# Lógica recomendada para um AI Agent processar pagamento: 1. CRIAR COBRANÇA   POST /api/payments   Body: { amount, expires_in_minutes: 15, description }   → Guardar: payment_id, pay_url 2. APRESENTAR AO UTILIZADOR   "Aceda a {pay_url} para efectuar o pagamento.    Envie o PDF comprovativo para o email mostrado na página." 3. MONITORIZAR STATUS (a cada 30 segundos)   GET /api/payments/{payment_id}   → status == "paid"    → Confirmar e entregar produto/serviço   → status == "expired" → Informar utilizador, criar nova cobrança   → status == "pending" → Continuar a monitorizar 4. ALTERNATIVA PREFERIDA: Webhook   Configurar POST /api/webhook/config com a sua URL   Ao receber event "payment.paid" → Confirmar imediatamente

Template de Prompt para Agentes

Você é um assistente de pagamento integrado com sPay. - API Key: {API_KEY} - Base URL: {BASE_URL} Quando solicitado a cobrar um cliente: 1. Chame POST /api/payments com o montante e 15 minutos de expiração. 2. Retorne o pay_url ao utilizador. 3. Rastreie o payment_id e reporte o status quando consultado. 4. Ao receber webhook event "payment.paid", confirme a transação.

Exemplos de Código Completo

Exemplos de integração completa prontos a usar — criar cobrança + receber e verificar webhook.

JSintegration.js
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
const express = require("express");const crypto = require("crypto"); const API_KEY = "spk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";const BASE_URL = "https://backend-phi-seven-54.vercel.app";const WEBHOOK_SECRET = "whsec_xxxxxxxxxxxxxxxxxxxxxxxx"; const app = express();app.use(express.json()); // 1. Criar cobrança no checkout da lojaapp.post("/checkout", async (req, res) => {  const { amount, description } = req.body;   const spay = await fetch(`${BASE_URL}/api/payments`, {    method: "POST",    headers: { "Content-Type": "application/json", "X-API-Key": API_KEY },    body: JSON.stringify({ amount, expires_in_minutes: 15, description }),  });   if (!spay.ok) return res.status(400).json({ error: "Erro ao criar cobrança" });   const payment = await spay.json();   // Guardar payment_id na base de dados da sua loja  await db.orders.update({ orderId: req.body.orderId }, {    paymentId: payment.payment_id,    paymentStatus: "pending"  });   res.json({ pay_url: payment.pay_url });}); // 2. Receber confirmação via Webhookapp.post("/webhooks/spay", (req, res) => {  const signature = req.headers["x-spay-signature"];  const payloadStr = JSON.stringify(req.body, Object.keys(req.body).sort());  const computed = crypto.createHmac("sha256", WEBHOOK_SECRET)    .update(payloadStr).digest("hex");   if (computed !== signature) return res.status(401).send("Inválido");   const { event, data } = req.body;  if (event === "payment.paid") {    // Activar o produto/serviço para o cliente    console.log(`Pagamento ${data.payment_id} de ${data.amount} Kz confirmado!`);    db.orders.update({ paymentId: data.payment_id }, { status: "paid" });  }   res.sendStatus(200);}); app.listen(3001, () => console.log("Servidor sPay integration a correr na porta 3001"));

Nota de Segurança:

Nunca exponha a X-API-Key no código de frontend (HTML/React no browser). Todas as chamadas para criar cobranças devem ser originadas do seu servidor seguro (Backend) para prevenir que terceiros gerem faturas não autorizadas em seu nome.