Quickstart de 5 minutos
Da conta nova a um pagamento Pix simulado com webhook assinado. Você precisa de curl ou Node.js 18+ (os exemplos em Node usam só a biblioteca padrão e fetch).
curl e cada arquivo Node abaixo roda na suíte de testes da API da Wolf a cada mudança, e as respostas são validadas contra o contrato publicado na referência.1. Crie sua conta
Abra https://v2.wolfpayteste.online/login, use Criar conta Seller (senha com 12+ caracteres), confirme o e-mail e conclua o onboarding com a razão social e o nome público que o comprador verá no checkout. No Wolf Sandbox público a conta é aprovada automaticamente e o painel abre em seguida.
2. Crie uma API key de teste
No painel, vá em API keys e crie uma chave com os escopos payments:read, payments:write e webhooks:write. O segredo sk_test_... aparece uma única vez: copie agora. Guarde-o só no servidor (nunca em navegador ou app). Detalhes em Autenticação.
export WOLF_API_URL="https://api.v2.wolfpayteste.online"
export WOLF_API_KEY="sk_test_..." # a chave copiada no passo 23. Prepare o receptor de webhooks
Rode o receptor abaixo e exponha-o numa URL HTTPS pública (por exemplo, um túnel HTTPS de sua preferência ou um servidor seu). Depois, no painel, em Webhooks, cadastre essa URL no modo live e copie o signing secret whsec_live_... — ele também aparece uma única vez.
// Passo 6 — receber o webhook e verificar a assinatura (Node.js puro, sem dependências).
// Uso: WOLF_WEBHOOK_SECRET=whsec_live_... PORT=3000 node receber-webhook.mjs
import crypto from 'node:crypto';
import http from 'node:http';
import { fileURLToPath } from 'node:url';
const TOLERANCE_SECONDS = 300;
/** true se `x-wolf-signature` confere com HMAC-SHA256(segredo, `${timestamp}.${corpo bruto}`) e o timestamp é recente. */
export function verifyWolfSignature(rawBody, timestamp, signature, secret, nowSeconds = Date.now() / 1000) {
if (!/^\d+$/.test(timestamp ?? '')) return false;
if (Math.abs(nowSeconds - Number(timestamp)) > TOLERANCE_SECONDS) return false;
const expected = 'v1=' + crypto.createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest('hex');
const a = Buffer.from(expected), b = Buffer.from(signature ?? '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
/** Servidor HTTP que aceita só entregas assinadas e chama `onEvent` uma vez por `event.id`. */
export function createWebhookServer({ secret, onEvent }) {
const seen = new Set();
return http.createServer((request, response) => {
if (request.method !== 'POST') { response.writeHead(405).end(); return; }
const chunks = [];
request.on('data', (chunk) => chunks.push(chunk));
request.on('end', () => {
const rawBody = Buffer.concat(chunks); // bytes exatos: não reserialize o JSON antes de verificar
if (!verifyWolfSignature(rawBody, request.headers['x-wolf-timestamp'], request.headers['x-wolf-signature'], secret)) {
response.writeHead(400).end();
return;
}
const event = JSON.parse(rawBody.toString('utf8'));
// Idempotência: a mesma entrega pode chegar mais de uma vez (retries, replays).
if (!seen.has(event.id)) { seen.add(event.id); onEvent(event); }
response.writeHead(204).end(); // responda 2xx rápido; processe em segundo plano
});
});
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const port = Number(process.env.PORT ?? 3000);
createWebhookServer({
secret: process.env.WOLF_WEBHOOK_SECRET,
onEvent: (event) => console.log(event.type, event.resource_id, event.data?.status ?? ''),
}).listen(port, () => console.log(`Ouvindo webhooks em http://localhost:${port}`));
}O receptor recusa (400) qualquer entrega sem assinatura válida ou com timestamp fora da janela de 5 minutos e processa cada event.id uma única vez. O formato completo está em Webhooks → assinatura.
4. Crie um PaymentIntent Pix com o pagador
O valor vai em centavos, como string. O documento do pagador é opcional, validado pelo dígito verificador e nunca volta em claro: a resposta traz só o tipo e os dois últimos dígitos.
# Passo 3 — criar um PaymentIntent Pix com pagador (amount_minor em centavos, como string).
# Use uma Idempotency-Key única por operação (por exemplo, derivada do id do seu pedido).
curl -sS -X POST "$WOLF_API_URL/v1/payment-intents" \
-H "Authorization: Bearer $WOLF_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: quickstart-A-1001-criar" \
-d '{
"amount_minor": "9900",
"currency": "BRL",
"payment_method_types": ["pix"],
"description": "Curso de fotografia",
"metadata": { "pedido_interno": "A-1001" },
"payer": {
"name": "Ana Compradora",
"email": "ana@example.com",
"document": { "type": "cpf", "number": "529.982.247-25" }
}
}'// Passo 3 — criar um PaymentIntent Pix com pagador.
// Uso: WOLF_API_URL=... WOLF_API_KEY=sk_test_... node criar-payment-intent.mjs
import { randomUUID } from 'node:crypto';
import { fileURLToPath } from 'node:url';
export async function createPixPaymentIntent({ apiUrl = process.env.WOLF_API_URL, apiKey = process.env.WOLF_API_KEY } = {}) {
const response = await fetch(`${apiUrl}/v1/payment-intents`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
// Uma chave por operação. Repetir a mesma chave com o mesmo corpo devolve o mesmo PaymentIntent.
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
amount_minor: '9900', // R$ 99,00 em centavos, sempre como string
currency: 'BRL',
payment_method_types: ['pix'],
description: 'Curso de fotografia',
metadata: { pedido_interno: 'A-1001' },
payer: {
name: 'Ana Compradora',
email: 'ana@example.com',
document: { type: 'cpf', number: '529.982.247-25' }, // opcional; nunca volta em claro
},
}),
});
const body = await response.json();
if (!response.ok) throw new Error(`Wolf API ${response.status}: ${body.error?.code} (request_id ${body.error?.request_id})`);
return body; // status "requires_payment_method"
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const paymentIntent = await createPixPaymentIntent();
console.log(paymentIntent.id, paymentIntent.status);
}A resposta é 201 com status: "requires_payment_method". Guarde o id: export PAYMENT_INTENT_ID=pi_....
5. Confirme com Pix
# Passo 4 — confirmar com Pix. Resposta 202: status "processing" e next_action.pix_qr (QR sintético SIMULADO).
curl -sS -X POST "$WOLF_API_URL/v1/payment-intents/$PAYMENT_INTENT_ID/confirm" \
-H "Authorization: Bearer $WOLF_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: quickstart-A-1001-confirmar" \
-d '{ "payment_method": { "type": "pix" } }'// Passo 4 — confirmar o PaymentIntent com Pix e obter o QR Code sintético.
// Uso: WOLF_API_URL=... WOLF_API_KEY=sk_test_... PAYMENT_INTENT_ID=pi_... node confirmar-pix.mjs
import { randomUUID } from 'node:crypto';
import { fileURLToPath } from 'node:url';
export async function confirmPix({ apiUrl = process.env.WOLF_API_URL, apiKey = process.env.WOLF_API_KEY, paymentIntentId = process.env.PAYMENT_INTENT_ID } = {}) {
const response = await fetch(`${apiUrl}/v1/payment-intents/${encodeURIComponent(paymentIntentId)}/confirm`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({ payment_method: { type: 'pix' } }),
});
const body = await response.json();
if (!response.ok) throw new Error(`Wolf API ${response.status}: ${body.error?.code} (request_id ${body.error?.request_id})`);
// 202: status "processing" e next_action.type "pix_qr" com o copia-e-cola SIMULADO e o vencimento.
return body;
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const paymentIntent = await confirmPix();
console.log(paymentIntent.status, paymentIntent.next_action?.expires_at);
console.log(paymentIntent.next_action?.qr_code_payload);
}A resposta é 202 com status: "processing" e next_action.type: "pix_qr": o copia-e-cola é sintético (marcado SIMULADO, não pagável em nenhum banco) e vence em 30 minutos. O receptor recebe payment.processing (se o endpoint escuta todos os eventos ou esse tipo).
6. Simule o pagamento
No sandbox, quem “paga” o Pix é você: a rota do simulador gera o mesmo evento de provedor que um banco geraria. O comprador também pode usar o botão Simular pagamento do checkout hospedado.
# Passo 5 — simular o pagamento do Pix (só existe no Wolf Sandbox). O PaymentIntent vira "succeeded".
curl -sS -X POST "$WOLF_API_URL/v1/simulator/payment-intents/$PAYMENT_INTENT_ID/pix/pay" \
-H "Authorization: Bearer $WOLF_API_KEY"
# Conferir o estado a qualquer momento:
curl -sS "$WOLF_API_URL/v1/payment-intents/$PAYMENT_INTENT_ID" \
-H "Authorization: Bearer $WOLF_API_KEY"// Passo 5 — simular o pagamento do Pix (só existe no Wolf Sandbox) e conferir o resultado.
// Uso: WOLF_API_URL=... WOLF_API_KEY=sk_test_... PAYMENT_INTENT_ID=pi_... node simular-pagamento.mjs
import { fileURLToPath } from 'node:url';
async function wolf(path, { apiUrl, apiKey, method = 'GET' }) {
const response = await fetch(`${apiUrl}${path}`, { method, headers: { Authorization: `Bearer ${apiKey}` } });
const body = await response.json();
if (!response.ok) throw new Error(`Wolf API ${response.status}: ${body.error?.code} (request_id ${body.error?.request_id})`);
return body;
}
export async function simulatePixPayment({ apiUrl = process.env.WOLF_API_URL, apiKey = process.env.WOLF_API_KEY, paymentIntentId = process.env.PAYMENT_INTENT_ID } = {}) {
const id = encodeURIComponent(paymentIntentId);
// Gera o evento de provedor pix.paid. Repetir não cobra de novo: o PaymentIntent continua succeeded.
await wolf(`/v1/simulator/payment-intents/${id}/pix/pay`, { apiUrl, apiKey, method: 'POST' });
return wolf(`/v1/payment-intents/${id}`, { apiUrl, apiKey }); // status "succeeded"
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const paymentIntent = await simulatePixPayment();
console.log(paymentIntent.id, paymentIntent.status);
}O PaymentIntent vira succeeded: a Charge é criada e o ledger é lançado na mesma transação. Repetir a simulação não cobra de novo.
7. Receba o webhook e confira a assinatura
Em até cerca de dois minutos o receptor imprime payment.paid com o id do PaymentIntent. Cada entrega traz x-wolf-event-id, x-wolf-timestamp e x-wolf-signature (v1= + HMAC-SHA256 do timestamp.corpo). Para testar só o receptor, peça um evento webhook.test:
# Passo 6 (opcional) — pedir um evento webhook.test para o seu endpoint (resposta 202, entrega em até ~2 min).
curl -sS -X POST "$WOLF_API_URL/v1/webhooks/endpoints/$WEBHOOK_ENDPOINT_ID/test" \
-H "Authorization: Bearer $WOLF_API_KEY" \
-H "Idempotency-Key: quickstart-teste-webhook-1"// Passo 6 (opcional) — pedir um evento webhook.test para o seu endpoint.
// Uso: WOLF_API_URL=... WOLF_API_KEY=sk_test_... WEBHOOK_ENDPOINT_ID=whe_... node enviar-evento-de-teste.mjs
import { randomUUID } from 'node:crypto';
import { fileURLToPath } from 'node:url';
export async function sendTestEvent({ apiUrl = process.env.WOLF_API_URL, apiKey = process.env.WOLF_API_KEY, endpointId = process.env.WEBHOOK_ENDPOINT_ID } = {}) {
const response = await fetch(`${apiUrl}/v1/webhooks/endpoints/${encodeURIComponent(endpointId)}/test`, {
method: 'POST',
headers: { Authorization: `Bearer ${apiKey}`, 'Idempotency-Key': randomUUID() },
});
const body = await response.json();
if (!response.ok) throw new Error(`Wolf API ${response.status}: ${body.error?.code} (request_id ${body.error?.request_id})`);
return body; // 202: entrega pendente; chega ao endpoint em até ~2 minutos
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const delivery = await sendTestEvent();
console.log(delivery.id, delivery.event_type, delivery.status);
}Próximos passos
- Cartões de teste: aprovado, recusas e indisponibilidade com reconciliação.
- Payment Links: venda sem código com o checkout hospedado.
- Idempotência e erros: como repetir com segurança.
- Referência da API: todas as operações.