Desenvolvedores
Checkout personalizado
O lojista que já tem gateway ou adquirente próprio pluga a própria API de cobrança no MeWeb. O checkout continua sendo o do MeWeb, transparente, sem redirecionar o comprador para lugar nenhum: quem muda é apenas quem processa o dinheiro por trás. A configuração fica em Configurações → Pagamentos → “API própria (custom)”, no admin da loja; esta página é o contrato que o seu servidor precisa cumprir.
Limites deliberados
- Sem split de pagamento. O MeWeb envia uma cobrança com um valor; a divisão entre recebedores, se existir, é problema do seu lado.
- Sem cofre de cartão no MeWeb. O token do cartão é o que você devolver, e ele não é reusado entre pedidos: cada compra tokeniza de novo.
- Sem antifraude do MeWeb. Análise de risco, chargeback, conciliação financeira e responsabilidade pela transação são inteiramente do lojista.
Os endpoints que você expõe
Três operações mais um ping de sanidade, todas sob o baseUrl que você cadastra no admin. O baseUrl é obrigatoriamente HTTPS; http:// só é aceito em ambiente de desenvolvimento.
| Operação | Chamada |
|---|---|
| Criar cobrança | POST {baseUrl}/charges |
| Estornar (total ou parcial) | POST {baseUrl}/refunds |
| Consultar status de uma cobrança | GET {baseUrl}/charges/{providerTransactionId} |
| Teste de conexão do admin | GET {baseUrl}/charges/ping |
Headers de toda requisição do MeWeb
Content-Type: application/json
X-MeWeb-Signature: t=<unixSeconds>,v1=<hex(hmac_sha256(secret, "<t>.<rawBody>"))>
X-MeWeb-Tenant: <tenantId>
Idempotency-Key: <ULID> # só na criação de cobrança
User-Agent: MeWeb/1.0O secret da assinatura é gerado pelo MeWeb na configuração do provedor. O Idempotency-Key acompanha apenas a criação de cobrança e é igual ao id do corpo: se a mesma chave chegar duas vezes, devolva a cobrança já criada em vez de criar outra.
Criar cobrança
O corpo é o formato canônico do MeWeb, não o de nenhum PSP. Valores em centavos, datas em ISO 8601 UTC. Traduza para o formato do seu gateway do seu lado.
{
"id": "01JBX9Q2M7ZK5C0W8V6H3T4RQ1",
"tenantId": "01JBX9K7Q2M4ZC0W8V6H3T4RQ0",
"orderRef": "01JBX9K7Q2M4ZC0W8V6H3T4RQ2",
"amountCents": 15990,
"currency": "BRL",
"method": "credit_card",
"installments": 3,
"customer": {
"name": "Maria Souza",
"email": "maria@exemplo.com.br",
"document": "12345678909",
"phone": "+5511999998888"
},
"billingAddress": {
"cep": "01310100",
"street": "Avenida Paulista",
"number": "1000",
"complement": "conj. 42",
"district": "Bela Vista",
"city": "São Paulo",
"state": "SP",
"country": "BR"
},
"card": { "token": "tok_abc123" },
"metadata": { "source": "meweb-checkout" }
}| Campo | Tipo | Descrição |
|---|---|---|
| id | string (ULID) | Id da cobrança no MeWeb: sirva-se dele para conciliar. É o mesmo valor do header Idempotency-Key. |
| tenantId | string (ULID) | Identificador da loja no MeWeb, o mesmo do header X-MeWeb-Tenant e da URL do webhook. |
| orderRef | string (ULID) | Referência do pedido que originou a cobrança. |
| amountCents | integer | Valor total em CENTAVOS: 15990 é R$ 159,90. Nunca decimal. |
| currency | string | Sempre "BRL". |
| method | enum | pix · credit_card · boleto. |
| installments | integer | Número de parcelas escolhido pelo comprador (1 quando à vista). |
| customer.name | string | Nome completo informado no checkout. |
| customer.email | string | E-mail do comprador. |
| customer.document | string | CPF ou CNPJ, apenas dígitos. |
| customer.phone | string | Telefone em formato E.164. |
| billingAddress | object | null | Endereço de cobrança (cep, street, number, complement, district, city, state, country). Vem null quando a loja não coletou endereço. |
| card | object | Só quando method é credit_card: { "token": "..." } com o token gerado pelo seu gateway. |
| pix | object | Só quando method é pix: { "expiresInSeconds": 3600 }. |
| boleto | object | Só quando method é boleto: { "dueDate": "2026-08-25T00:00:00.000Z" } (ISO 8601 UTC). |
| metadata | object | Pares livres enviados pelo MeWeb. Devolva-os no seu registro se quiser. |
cardsó vai quandomethodécredit_card. Parapixo corpo traz"pix": { "expiresInSeconds": 3600 }; paraboleto,"boleto": { "dueDate": "2026-08-25T00:00:00.000Z" }.billingAddresspode virnullquando a loja não coletou endereço; não assuma que ele existe.
Resposta esperada
HTTP 200 ou 201, corpo JSON:
{
"providerTransactionId": "ch_9f3c21",
"status": "APPROVED",
"pixQrCode": null,
"pixCode": null,
"boletoUrl": null,
"boletoLine": null,
"expiresAt": null,
"failureCode": null,
"failureMessage": null
}providerTransactionId(obrigatório) é o id da cobrança no seu sistema. É por ele que o MeWeb consulta status, estorna e casa o webhook.statusé o seu status cru: o MeWeb traduz pelostatusMap.- Pix: devolva
pixCode(copia-e-cola) e, se tiver,pixQrCode. OexpiresAtvai em ISO 8601. - Boleto: devolva
boletoUrl(PDF) eboletoLine(linha digitável). - Recusa: devolva um
statusque mapeie parafailed, maisfailureCodeefailureMessage. O MeWeb nunca mostra afailureMessagecrua ao comprador: ela vira uma mensagem em pt-BR acionável; o valor cru serve ao seu log e à sua investigação.
Como o MeWeb chama
curl -X POST "https://pagamentos.sua-loja.com.br/charges" \
-H "Content-Type: application/json" \
-H "X-MeWeb-Signature: t=1770000000,v1=5257a869e7ec..." \
-H "X-MeWeb-Tenant: 01JBX9K7Q2M4ZC0W8V6H3T4RQ0" \
-H "Idempotency-Key: 01JBX9Q2M7ZK5C0W8V6H3T4RQ1" \
-H "User-Agent: MeWeb/1.0" \
-d '{
"id": "01JBX9Q2M7ZK5C0W8V6H3T4RQ1",
"tenantId": "01JBX9K7Q2M4ZC0W8V6H3T4RQ0",
"orderRef": "01JBX9K7Q2M4ZC0W8V6H3T4RQ2",
"amountCents": 15990,
"currency": "BRL",
"method": "pix",
"installments": 1,
"customer": {
"name": "Maria Souza",
"email": "maria@exemplo.com.br",
"document": "12345678909",
"phone": "+5511999998888"
},
"billingAddress": null,
"pix": { "expiresInSeconds": 3600 },
"metadata": { "source": "meweb-checkout" }
}'Estornar
POST {baseUrl}/refunds
{ "providerTransactionId": "ch_9f3c21", "amountCents": 5000 }Estorno parcial é simplesmente um amountCents menor que o total da cobrança.
{ "refundId": "re_77a1", "status": "refunded" }Aqui o status aceita refunded · pending · failed, apenas esses três e sempre canônicos: o estorno não passa pelo statusMap. failed faz a operação inteira falhar no MeWeb, sem estado intermediário.
Consultar status
GET {baseUrl}/charges/ch_9f3c21
{ "providerTransactionId": "ch_9f3c21", "status": "APPROVED" }É a rota usada pela reconciliação: um job roda a cada 15 minutos sobre as cobranças que estão pending há mais de 10 minutos e pergunta ao seu servidor o estado real.
Teste de conexão
GET {baseUrl}/charges/ping
200 OK
{ "ok": true }Precisa responder 200 com um corpo JSON qualquer. É o botão “Testar conexão” do admin, e o provedor custom só pode ser ativado com esse teste verde.
statusMap
O MeWeb não adivinha o vocabulário do seu gateway: você declara no admin o mapeamento dos seus status para os canônicos da plataforma.
{
"APPROVED": "approved",
"DENIED": "failed",
"WAITING": "pending"
}Canônicos válidos: pending · authorized · approved · failed · cancelled · refunded · partially_refunded.
A busca é case-insensitive. Status fora do mapa não muda a cobrança: o fato vira log de aviso e a cobrança cai na reconciliação: o MeWeb nunca chuta um estado.
Webhook: você avisando o MeWeb
POST https://sistema-<sua-loja>.meweb.com.br/api/v1/webhooks/payments/custom/<tenantId>
Content-Type: application/json
X-MeWeb-Signature: t=1770000000,v1=5257a869e7ec...
{ "id": "evt_123", "providerTransactionId": "ch_9f3c21", "status": "APPROVED" }A URL exata, já com o seu tenantId, é copiável no admin. O webhook é assinado com o mesmo esquema X-MeWeb-Signature e o mesmo segredo das chamadas de saída.
idé opcional e serve à idempotência: evento repetido é descartado.- Assinatura inválida ou ausente → 401, e nada muda. Deriva de relógio maior que 5 minutos → 401 também.
- O processamento é idempotente por (
providerTransactionId,status).
Verificando e gerando a assinatura
O rawBody tem de ser o corpo bruto recebido, byte a byte, nunca um JSON interpretado e re-serializado: a re-serialização muda os bytes e a verificação falha. É o mesmo esquema dos webhooks da loja.
const crypto = require('node:crypto');
function verifyMewebSignature(header, rawBody, secret) {
const parts = Object.fromEntries(
String(header ?? '')
.split(',')
.map((p) => p.split('=').map((s) => s.trim())),
);
const t = Number(parts.t);
if (!Number.isFinite(t)) return false;
// Janela de 5 minutos contra replay.
if (Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 ?? '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}const t = Math.floor(Date.now() / 1000);
const body = JSON.stringify({ providerTransactionId: 'ch_9f3c21', status: 'APPROVED' });
const v1 = crypto.createHmac('sha256', secret).update(`${t}.${body}`).digest('hex');
// header: `X-MeWeb-Signature: t=${t},v1=${v1}`Timeouts e falhas
- 15 segundos por chamada, e sem retry automático na criação: retry duplicaria cobrança. Quem retenta é o comprador, até 3 tentativas antes de o checkout sugerir outro método de pagamento.
- Timeout na criação ⇒ a cobrança fica
pendingno MeWeb e a reconciliação resolve. Se o seu servidor criou a cobrança mesmo assim, ela será encontrada. - Consulta de status e reconciliação retentam com backoff, porque essas são idempotentes por natureza.
- Qualquer resposta não-2xx ⇒ a cobrança não é criada e o comprador vê a mensagem padrão em pt-BR.
- Redirects não são seguidos. O
baseUrlprecisa apontar direto para o seu servidor. - O
baseUrlpassa por validação anti-SSRF: hosts que resolvem para rede interna ou reservada (127.0.0.0/8,10/8,172.16/12,192.168/16,169.254/16, CGNAT etc.) são recusados.
Segurança
- O segredo de assinatura é gerado pelo MeWeb, exibido uma única vez e guardado cifrado. Combine um canal seguro para transportá-lo até o seu servidor.
- O admin tem botão de regerar: o segredo anterior é invalidado na hora e um novo teste de conexão passa a ser exigido.
- Rejeite assinatura inválida e deriva de relógio maior que 5 minutos. Mantenha o relógio do seu servidor sincronizado por NTP.
- Nunca envie o PAN do cartão para a API do MeWeb: só o token. Dados de cartão não trafegam nem são armazenados pela plataforma.