Introdução
A API do DePix App permite criar e gerenciar checkouts Pix programaticamente. É a mesma API que alimenta o plugin do BTCPay Server e a Área do Lojista.
URL base
https://depix-backend.vercel.app
Formato
Todos os requests e responses usam JSON (Content-Type: application/json). Valores monetários são sempre em centavos (inteiros). Exemplo: R$ 10,00 = 1000.
Autenticação
Use uma API key no header Authorization em todos os requests autenticados.
Authorization: Bearer sk_live_<sua-chave>
Tipos de chave
| Prefixo | Tipo | Comportamento |
|---|---|---|
| sk_live_ | Live | Checkouts reais. Dinheiro de verdade. |
| sk_test_ | Test | Checkouts de teste. Nenhum dinheiro real é movimentado. |
Para gerenciar suas chaves (criar, listar, revogar), acesse a Área do Lojista em depixapp.com/#merchant. Máximo de 5 chaves live e 5 chaves test ativas por conta.
Erros
Erros seguem o formato abaixo. O campo errorMessage é sempre legível para humanos.
{
"response": {
"errorMessage": "Dados inválidos.",
"errors": [ // presente em erros de validação
{ "field": "amount", "message": "Obrigatório." }
]
}
}
| Status | Significado |
|---|---|
| 400 | Dados inválidos — verifique os campos. |
| 401 | API key ausente, inválida ou revogada. |
| 403 | Sem permissão. Conta de lojista não configurada, ou chave sem acesso ao recurso. |
| 404 | Recurso não encontrado. |
| 409 | Conflito de estado — operação não permitida no status atual. |
| 429 | Rate limit excedido. Aguarde e tente novamente. |
| 500 | Erro interno no servidor. Tente novamente em alguns instantes. |
| 503 | Serviço indisponível — plataforma em manutenção ou provedor Pix temporariamente fora do ar. |
Criar checkout
Cria um novo checkout Pix. Retorna o QR code e a URL de pagamento para exibir ao seu cliente.
Parâmetros
| Campo | Tipo | Descrição | |
|---|---|---|---|
| amount | integer | obrigatório | Valor em centavos. Mínimo: 500 (R$ 5,00). Máximo: 300000 (R$ 3.000,00). |
| description | string | opcional | Descrição do pedido. Máximo 500 caracteres. Exibida na página de pagamento. |
| expires_in | integer | opcional | Tempo de expiração em segundos. Padrão: 1200 (20min). Mínimo: 300 (5min). Máximo: 1200 (20min). |
| image_url | string | opcional | URL HTTPS da imagem do produto. Exibida na página de pagamento. |
| callback_url | string | opcional | URL HTTPS que recebe os webhooks do checkout. |
| redirect_url | string | opcional | URL para redirecionar o cliente após o pagamento. |
| metadata | object | opcional | Dados adicionais do seu sistema (order_id, user_id, etc.). Máximo 4KB. Devolvido nos webhooks. |
Exemplo
curl -X POST https://depix-backend.vercel.app/api/checkouts \ -H "Authorization: Bearer sk_live_<sua-chave>" \ -H "Content-Type: application/json" \ -d '{ "amount": 2990, "description": "Camiseta tamanho M", "expires_in": 900, "callback_url": "https://minha-loja.com/webhook/depix", "metadata": { "order_id": "ORD-123" } }'
const res = await fetch("https://depix-backend.vercel.app/api/checkouts", { method: "POST", headers: { "Authorization": "Bearer sk_live_<sua-chave>", "Content-Type": "application/json", }, body: JSON.stringify({ amount: 2990, description: "Camiseta tamanho M", expires_in: 900, callback_url: "https://minha-loja.com/webhook/depix", metadata: { order_id: "ORD-123" }, }), }); const data = await res.json(); console.log(data.id, data.payment_url);
import requests resp = requests.post( "https://depix-backend.vercel.app/api/checkouts", headers={"Authorization": "Bearer sk_live_<sua-chave>"}, json={ "amount": 2990, "description": "Camiseta tamanho M", "expires_in": 900, "callback_url": "https://minha-loja.com/webhook/depix", "metadata": {"order_id": "ORD-123"}, }, ) data = resp.json() print(data["id"], data["payment_url"])
$ch = curl_init("https://depix-backend.vercel.app/api/checkouts"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer sk_live_<sua-chave>", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "amount" => 2990, "description" => "Camiseta tamanho M", "expires_in" => 900, "callback_url" => "https://minha-loja.com/webhook/depix", "metadata" => ["order_id" => "ORD-123"], ]), ]); $response = curl_exec($ch); $data = json_decode($response, true); echo $data["id"] . " " . $data["payment_url"];
using var client = new HttpClient(); client.DefaultRequestHeaders.Add("Authorization", "Bearer sk_live_<sua-chave>"); var payload = new { amount = 2990, description = "Camiseta tamanho M", expires_in = 900, callback_url = "https://minha-loja.com/webhook/depix", metadata = new { order_id = "ORD-123" } }; var res = await client.PostAsync( "https://depix-backend.vercel.app/api/checkouts", new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json") ); var json = await res.Content.ReadAsStringAsync(); Console.WriteLine(json);
body := `{"amount":2990,"description":"Camiseta tamanho M","expires_in":900,"callback_url":"https://minha-loja.com/webhook/depix","metadata":{"order_id":"ORD-123"}}` req, _ := http.NewRequest("POST", "https://depix-backend.vercel.app/api/checkouts", strings.NewReader(body)) req.Header.Set("Authorization", "Bearer sk_live_<sua-chave>") req.Header.Set("Content-Type", "application/json") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() io.Copy(os.Stdout, resp.Body)
require "net/http" require "json" uri = URI("https://depix-backend.vercel.app/api/checkouts") req = Net::HTTP::Post.new(uri, { "Authorization" => "Bearer sk_live_<sua-chave>", "Content-Type" => "application/json", }) req.body = { amount: 2990, description: "Camiseta tamanho M", expires_in: 900, callback_url: "https://minha-loja.com/webhook/depix", metadata: { order_id: "ORD-123" } }.to_json res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } puts JSON.parse(res.body)
HttpClient client = HttpClient.newHttpClient(); String json = """ {"amount":2990,"description":"Camiseta tamanho M","expires_in":900, "callback_url":"https://minha-loja.com/webhook/depix", "metadata":{"order_id":"ORD-123"}}"""; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://depix-backend.vercel.app/api/checkouts")) .header("Authorization", "Bearer sk_live_<sua-chave>") .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body());
{
"id": "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"status": "pending",
"amount": 2990,
"description": "Camiseta tamanho M",
"image_url": null,
"expires_at": "2025-06-01T15:30:00.000Z",
"is_live": true,
"payment_url": "https://pay.depixapp.com/chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"pix": {
"qr_code": "00020126580014br.gov.bcb.pix..." // payload EMV para QR code
}
}
payment_url ao seu cliente ou gere um QR code a partir do pix.qr_code. O QR code é compatível com qualquer app de banco.
Consultar checkout
Retorna os detalhes de um checkout específico.
curl https://depix-backend.vercel.app/api/checkouts/chk_01jxxxxxxxxxxxxxxxxxxxxxx \ -H "Authorization: Bearer sk_live_<sua-chave>"
{
"checkout": {
"id": "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"status": "completed", // pending | processing | completed | cancelled | expired
"amount": 2990,
"description": "Camiseta tamanho M",
"image_url": null,
"callback_url": "https://minha-loja.com/webhook/depix",
"redirect_url": null,
"metadata": { "order_id": "ORD-123" },
"expires_at": "2025-06-01T15:30:00.000Z",
"is_live": true,
"created_at": "2025-06-01T15:00:00.000Z",
"processing_at": "2025-06-01T15:02:00.000Z",
"completed_at": "2025-06-01T15:22:00.000Z",
"cancelled_at": null,
"blockchain_tx_id": "abc123...def456" // txid Liquid (presente quando completed)
}
}
Status possíveis
| Status | Significado |
|---|---|
| pending | Aguardando pagamento. |
| processing | Pix recebido, processando conversão para DePix. |
| completed | Pagamento confirmado. DePix na carteira do merchant. |
| cancelled | Cancelado pelo merchant. |
| expired | Prazo de pagamento expirou. |
Listar checkouts
Lista os checkouts do merchant com filtros e paginação.
Query params (todos opcionais)
| Parâmetro | Descrição |
|---|---|
| status | Filtrar por status: pending, completed, cancelled, expired. |
| product_id | Filtrar por produto. Ex: prd_xxx. |
| from | Data de início (ISO 8601). Ex: 2025-06-01T00:00:00Z. |
| to | Data de fim (ISO 8601). |
| q | Busca por ID ou descrição. |
| limit | Número de resultados por página. Padrão: 50. Máximo: 100. |
| offset | Paginação. Padrão: 0. |
curl "https://depix-backend.vercel.app/api/checkouts?status=completed&limit=20" \ -H "Authorization: Bearer sk_live_<sua-chave>"
{
"checkouts": [
{
"id": "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"status": "completed",
"amount": 2990,
"description": "Camiseta tamanho M",
"product_name": "Camiseta Preta", // null se checkout não vier de um produto
"metadata": "{\"order_id\":\"42\"}", // string JSON, null se ausente
"created_at": "2025-06-01T15:00:00.000Z",
"processing_at": "2025-06-01T15:02:14.000Z",
"expires_at": "2025-06-01T15:30:00.000Z",
"is_live": true
}
],
"stats": {
"total": 47,
"pending": 2,
"completed": 40,
"completed_amount": 189500 // centavos — R$ 1.895,00
},
"limit": 20,
"offset": 0
}
Cancelar checkout
Cancela um checkout pendente. Só é possível cancelar checkouts com status pending.
curl -X POST https://depix-backend.vercel.app/api/checkouts/chk_01jxxxxxxxxxxxxxxxxxxxxxx/cancel \ -H "Authorization: Bearer sk_live_<sua-chave>"
{ "success": true }
Criar produto
Cria um novo produto com valor fixo. Cada produto gera um link de pagamento permanente que pode ser compartilhado com seus clientes.
Parâmetros
| Campo | Tipo | Descrição | |
|---|---|---|---|
| name | string | obrigatório | Nome do produto exibido na UI. 2-80 caracteres. |
| slug | string | opcional | Identificador na URL. Se omitido, é gerado automaticamente a partir do name. Letras minúsculas, números e hífens. 2-60 caracteres. Não pode começar/terminar com hífen. |
| amount | integer | obrigatório | Valor em centavos. Mínimo: 500. Máximo: 300000. |
| description | string | opcional | Descrição do produto. Máximo 500 caracteres. |
| image_url | string | opcional | URL HTTPS da imagem do produto. |
| callback_url | string | opcional | URL HTTPS para webhooks. Sobrescreve o default do merchant. |
| redirect_url | string | opcional | URL de redirecionamento. Sobrescreve o default do merchant. |
| metadata | object | opcional | Dados adicionais. Máximo 4KB. Incluído nos webhooks dos checkouts gerados. |
| expires_in | integer | opcional | Tempo de expiração dos checkouts em segundos. Padrão: 1200 (20min). Mínimo: 300 (5min). Máximo: 1200 (20min). |
Exemplo
curl -X POST https://depix-backend.vercel.app/api/products \ -H "Authorization: Bearer sk_live_<sua-chave>" \ -H "Content-Type: application/json" \ -d '{ "name": "Camiseta M", "slug": "camiseta-m", "amount": 2990, "description": "Camiseta tamanho M" }'
const res = await fetch("https://depix-backend.vercel.app/api/products", { method: "POST", headers: { "Authorization": "Bearer sk_live_<sua-chave>", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Camiseta M", slug: "camiseta-m", amount: 2990, description: "Camiseta tamanho M", }), }); const data = await res.json(); console.log(data.product.payment_url);
import requests resp = requests.post( "https://depix-backend.vercel.app/api/products", headers={"Authorization": "Bearer sk_live_<sua-chave>"}, json={ "name": "Camiseta M", "slug": "camiseta-m", "amount": 2990, "description": "Camiseta tamanho M", }, ) data = resp.json() print(data["product"]["payment_url"])
$ch = curl_init("https://depix-backend.vercel.app/api/products"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer sk_live_<sua-chave>", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "name" => "Camiseta M", "slug" => "camiseta-m", "amount" => 2990, "description" => "Camiseta tamanho M", ]), ]); $response = curl_exec($ch); $data = json_decode($response, true); echo $data["product"]["payment_url"];
using var client = new HttpClient(); client.DefaultRequestHeaders.Add("Authorization", "Bearer sk_live_<sua-chave>"); var payload = new { name = "Camiseta M", slug = "camiseta-m", amount = 2990, description = "Camiseta tamanho M" }; var res = await client.PostAsync( "https://depix-backend.vercel.app/api/products", new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json") ); Console.WriteLine(await res.Content.ReadAsStringAsync());
body := `{"name":"Camiseta M","slug":"camiseta-m","amount":2990,"description":"Camiseta tamanho M"}` req, _ := http.NewRequest("POST", "https://depix-backend.vercel.app/api/products", strings.NewReader(body)) req.Header.Set("Authorization", "Bearer sk_live_<sua-chave>") req.Header.Set("Content-Type", "application/json") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() io.Copy(os.Stdout, resp.Body)
require "net/http" require "json" uri = URI("https://depix-backend.vercel.app/api/products") req = Net::HTTP::Post.new(uri, { "Authorization" => "Bearer sk_live_<sua-chave>", "Content-Type" => "application/json", }) req.body = { name: "Camiseta M", slug: "camiseta-m", amount: 2990, description: "Camiseta tamanho M" }.to_json res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } puts JSON.parse(res.body)
HttpClient client = HttpClient.newHttpClient(); String json = """ {"name":"Camiseta M","slug":"camiseta-m","amount":2990,"description":"Camiseta tamanho M"}"""; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://depix-backend.vercel.app/api/products")) .header("Authorization", "Bearer sk_live_<sua-chave>") .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body());
{
"product": {
"id": "prd_xxx",
"name": "Camiseta M",
"slug": "camiseta-m",
"amount": 2990,
"description": "Camiseta tamanho M",
"image_url": null,
"callback_url": null,
"redirect_url": null,
"metadata": null,
"expires_in": 1200,
"active": true,
"is_live": true,
"payment_url": "https://pay.depixapp.com/joao/camiseta-m"
}
}
Listar produtos
Lista os produtos do merchant com filtros e paginação.
Query params (todos opcionais)
| Parâmetro | Descrição |
|---|---|
| active | Filtrar por status: 1 (ativos) ou 0 (inativos). |
| q | Busca por nome, slug ou descrição. |
| limit | Número de resultados. Padrão: 50. Máximo: 100. |
| offset | Paginação. Padrão: 0. |
curl "https://depix-backend.vercel.app/api/products?active=1" \ -H "Authorization: Bearer sk_live_<sua-chave>"
{
"products": [
{
"id": "prd_xxx",
"name": "Camiseta M",
"slug": "camiseta-m",
"amount": 2990,
"description": "Camiseta tamanho M",
"active": true,
"is_live": true,
"payment_url": "https://pay.depixapp.com/joao/camiseta-m"
}
],
"stats": {
"total": 5,
"active": 4
},
"limit": 50,
"offset": 0
}
Consultar produto
Retorna os detalhes de um produto específico, incluindo estatísticas de checkouts.
curl https://depix-backend.vercel.app/api/products/prd_xxx \ -H "Authorization: Bearer sk_live_<sua-chave>"
{
"product": {
"id": "prd_xxx",
"name": "Camiseta M",
"slug": "camiseta-m",
"amount": 2990,
"description": "Camiseta tamanho M",
"image_url": null,
"callback_url": null,
"redirect_url": null,
"metadata": null,
"expires_in": 1200,
"active": true,
"is_live": true,
"payment_url": "https://pay.depixapp.com/joao/camiseta-m",
"created_at": "2025-06-01T00:00:00.000Z"
}
}
Editar produto
Atualiza um ou mais campos de um produto existente. Envie apenas os campos que deseja alterar.
Parâmetros (todos opcionais)
| Campo | Tipo | Descrição |
|---|---|---|
| name | string | Novo nome do produto. 2-80 caracteres. |
| slug | string | Novo identificador na URL. Mesmas regras da criação. |
| amount | integer | Novo valor em centavos. |
| description | string | Nova descrição. |
| image_url | string | Nova URL de imagem. |
| callback_url | string | Nova URL de webhook. |
| redirect_url | string | Nova URL de redirecionamento. |
| metadata | object | Novos dados adicionais. |
| expires_in | integer | Novo tempo de expiração dos checkouts. Mínimo: 300 (5min). Máximo: 1200 (20min). |
curl -X PATCH https://depix-backend.vercel.app/api/products/prd_xxx \ -H "Authorization: Bearer sk_live_<sua-chave>" \ -H "Content-Type: application/json" \ -d '{ "amount": 3490, "description": "Camiseta tamanho M - Edição Especial" }'
{
"product": {
"id": "prd_xxx",
"name": "Camiseta M",
"slug": "camiseta-m",
"amount": 3490,
"description": "Camiseta tamanho M - Edição Especial",
"active": true,
"is_live": true,
"payment_url": "https://pay.depixapp.com/joao/camiseta-m"
}
}
Ativar / Desativar produto
Ativa ou desativa um produto. Produtos inativos retornam erro 404 quando acessados pelo link de pagamento.
curl -X POST https://depix-backend.vercel.app/api/products/prd_xxx/activate \ -H "Authorization: Bearer sk_live_<sua-chave>"
curl -X POST https://depix-backend.vercel.app/api/products/prd_xxx/deactivate \ -H "Authorization: Bearer sk_live_<sua-chave>"
{ "success": true }
Checkouts do produto
Lista os checkouts gerados a partir de um produto específico. Aceita os mesmos filtros da listagem geral de checkouts.
curl "https://depix-backend.vercel.app/api/products/prd_xxx/checkouts?status=completed" \ -H "Authorization: Bearer sk_live_<sua-chave>"
A resposta segue o mesmo formato da listagem de checkouts.
Links de pagamento
O DePix App gera links de pagamento permanentes para produtos e para a página do merchant. Esses links criam checkouts sob demanda quando o cliente acessa.
Tipos de link
| Tipo | URL | Comportamento |
|---|---|---|
| Produto | https://pay.depixapp.com/{username}/{slug} | Valor fixo. O cliente vê o produto e clica "Pagar com PIX". |
| Merchant | https://pay.depixapp.com/{username} | Valor livre. O cliente digita o valor e clica "Pagar com PIX". |
Ciclo de vida
- Quando o cliente acessa o link e inicia o pagamento, um checkout individual é criado automaticamente.
- A partir daí, o ciclo de vida é idêntico a um checkout criado via API (status, webhooks, expiração).
- O
callback_urlsegue a cadeia: campo do produto (se houver) → default do merchant → null. - O
redirect_urlsegue a mesma cadeia.
Produto público
Retorna os dados públicos de um produto ativo. Não requer autenticação.
curl https://depix-backend.vercel.app/api/products/prd_xxx/public
{
"product": {
"id": "prd_xxx",
"name": "Camiseta M",
"slug": "camiseta-m",
"amount": 2990,
"description": "Camiseta tamanho M",
"image_url": null
},
"merchant": {
"name": "Loja do Joao",
"username": "joao"
}
}
Checkout do produto
Cria um checkout a partir de um produto ativo. Não requer autenticação. O valor é herdado do produto.
curl -X POST https://depix-backend.vercel.app/api/products/prd_xxx/checkout
A resposta segue o mesmo formato do criar checkout (status 201).
Página do merchant
Retorna os dados públicos do merchant. Não requer autenticação.
curl https://depix-backend.vercel.app/api/merchants/joao/public
{
"merchant": {
"name": "Loja do Joao",
"username": "joao"
}
}
Checkout do merchant
Cria um checkout com valor customizado a partir da página do merchant. Não requer autenticação.
Parâmetros
| Campo | Tipo | Descrição | |
|---|---|---|---|
| amount | integer | obrigatório | Valor em centavos. Mínimo: 500. Máximo: 300000. |
curl -X POST https://depix-backend.vercel.app/api/merchants/joao/checkout \ -H "Content-Type: application/json" \ -d '{ "amount": 5000 }'
A resposta segue o mesmo formato do criar checkout (status 201).
Webhooks
Quando o status de um checkout muda, a API envia um POST para o callback_url que você informou ao criar o checkout (ou configurado no produto/merchant).
Como funciona
- O request é enviado com timeout de 5 segundos.
- Se falhar, a API tenta novamente até 2 vezes: após 1 minuto e após 10 minutos (3 tentativas no total).
- Sua endpoint deve responder com status 2xx para confirmar recebimento.
- O
callback_urlprecisa ser HTTPS e de acesso público (sem IPs privados).
Eventos
checkout.processing
Disparado quando o pagamento Pix é recebido e a conversão está sendo processada.
{
"event": "checkout.processing",
"data": {
"id": "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"status": "processing",
"amount": 2990,
"processing_at": "2025-06-01T15:02:00.000Z",
"metadata": { "order_id": "ORD-123" }
}
}
checkout.completed
Disparado quando o pagamento é confirmado e o DePix chega na carteira do merchant.
{
"event": "checkout.completed",
"data": {
"id": "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"status": "completed",
"amount": 2990,
"completed_at": "2025-06-01T15:22:00.000Z",
"metadata": { "order_id": "ORD-123" }
}
}
checkout.cancelled
Disparado quando o merchant cancela o checkout via API.
{
"event": "checkout.cancelled",
"data": {
"id": "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"status": "cancelled",
"amount": 2990,
"cancelled_at": "2025-06-01T15:05:00.000Z",
"metadata": { "order_id": "ORD-123" }
}
}
checkout.expired
Disparado quando o checkout expira sem receber pagamento.
{
"event": "checkout.expired",
"data": {
"id": "chk_01jxxxxxxxxxxxxxxxxxxxxxx",
"status": "expired",
"amount": 2990,
"expires_at": "2025-06-01T15:30:00.000Z",
"metadata": { "order_id": "ORD-123" }
}
}
Verificar assinatura
Cada webhook vem com um header X-DePix-Signature. Sempre valide a assinatura antes de processar o evento — isso garante que o request veio da API do DePix App e não de terceiros.
Formato do header
X-DePix-Signature: t=1717257600,v1=abc123def456...
- t — timestamp Unix do envio (segundos).
- v1 — assinatura HMAC-SHA256 em hexadecimal.
Como validar
A assinatura é calculada sobre a string timestamp.payload usando o Webhook Secret da sua conta (disponível na Área do Lojista).
# Calcular a assinatura esperada EXPECTED=$(echo -n "${TIMESTAMP}.${RAW_BODY}" | \ openssl dgst -sha256 -hmac "${WEBHOOK_SECRET}" | awk '{print $2}') # Comparar com o v1 recebido if [ "$EXPECTED" = "$RECEIVED_V1" ]; then echo "Assinatura válida" fi
import crypto from "node:crypto"; function verifyWebhook(rawBody, sigHeader, secret) { const parts = Object.fromEntries( sigHeader.split(",").map(p => p.split("=", 2)) ); const timestamp = parts["t"]; const received = parts["v1"]; const expected = crypto .createHmac("sha256", secret) .update(`${timestamp}.${rawBody}`) .digest("hex"); // Use timingSafeEqual to prevent timing attacks const a = Buffer.from(expected, "hex"); const b = Buffer.from(received, "hex"); if (a.length !== b.length) return false; return crypto.timingSafeEqual(a, b); } // Exemplo com Express app.post("/webhook/depix", express.raw({ type: "application/json" }), (req, res) => { const sig = req.headers["x-depix-signature"]; if (!verifyWebhook(req.body.toString(), sig, process.env.DEPIX_WEBHOOK_SECRET)) { return res.status(401).send("Assinatura inválida"); } const { event, data } = JSON.parse(req.body); // processa o evento... res.sendStatus(200); });
import hmac, hashlib def verify_webhook(raw_body: str, sig_header: str, secret: str) -> bool: parts = dict(p.split("=", 1) for p in sig_header.split(",")) timestamp = parts["t"] received = parts["v1"] expected = hmac.new( secret.encode(), f"{timestamp}.{raw_body}".encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, received)
function verifyWebhook(string $rawBody, string $sigHeader, string $secret): bool { $parts = []; foreach (explode(",", $sigHeader) as $pair) { [$k, $v] = explode("=", $pair, 2); $parts[$k] = $v; } $expected = hash_hmac("sha256", $parts["t"] . "." . $rawBody, $secret); return hash_equals($expected, $parts["v1"]); }
static bool VerifyWebhook(string rawBody, string sigHeader, string secret) { var parts = sigHeader.Split(',') .ToDictionary(p => p.Split('=', 2)[0], p => p.Split('=', 2)[1]); using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); var expected = Convert.ToHexString( hmac.ComputeHash(Encoding.UTF8.GetBytes($"{parts["t"]}.{rawBody}")) ).ToLower(); return CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(parts["v1"]) ); }
func verifyWebhook(rawBody, sigHeader, secret string) bool { parts := make(map[string]string) for _, p := range strings.Split(sigHeader, ",") { kv := strings.SplitN(p, "=", 2) parts[kv[0]] = kv[1] } mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(parts["t"] + "." + rawBody)) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(expected), []byte(parts["v1"])) }
def verify_webhook(raw_body, sig_header, secret) parts = sig_header.split(",").to_h { |p| p.split("=", 2) } expected = OpenSSL::HMAC.hexdigest("sha256", secret, "#{parts['t']}.#{raw_body}") Rack::Utils.secure_compare(expected, parts["v1"]) end
static boolean verifyWebhook(String rawBody, String sigHeader, String secret) throws Exception { Map<String, String> parts = new HashMap<>(); for (String p : sigHeader.split(",")) { String[] kv = p.split("=", 2); parts.put(kv[0], kv[1]); } Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256")); String expected = HexFormat.of().formatHex( mac.doFinal((parts.get("t") + "." + rawBody).getBytes()) ); return MessageDigest.isEqual(expected.getBytes(), parts.get("v1").getBytes()); }
Sandbox
Use chaves do tipo sk_test_... para testar sem movimentar dinheiro real. Checkouts criados com chave test nunca geram Pix real e ficam isolados dos checkouts de produção.
Diferenças do modo test
- O campo
is_liveretornafalse. - O QR code gerado não é um Pix válido — não pode ser pago num app de banco.
- Use o endpoint
/simulate-paymentpara marcar o checkout como pago. - Webhooks são enviados normalmente — ótimo para testar sua integração de ponta a ponta.
Simular pagamento
Marca um checkout de teste como pago. Só funciona com chaves sk_test_. Dispara o webhook checkout.completed normalmente.
# 1. Crie um checkout de teste curl -X POST https://depix-backend.vercel.app/api/checkouts \ -H "Authorization: Bearer sk_test_<sua-chave>" \ -H "Content-Type: application/json" \ -d '{ "amount": 1000, "callback_url": "https://minha-loja.com/webhook" }' # 2. Simule o pagamento curl -X POST https://depix-backend.vercel.app/api/checkouts/chk_01jxxxxxxxxxxxxxxxxxxxxxx/simulate-payment \ -H "Authorization: Bearer sk_test_<sua-chave>"
{ "success": true }
Após a simulação, seu callback_url receberá o evento checkout.completed em alguns segundos — exatamente como num pagamento real.
Verificar chave (GET /api/me)
Retorna as informações do merchant autenticado. Útil para verificar se a API key é válida e consultar dados da conta.
curl https://depix-backend.vercel.app/api/me \ -H "Authorization: Bearer sk_live_<sua-chave>"
{
"merchant_id": "mrc_xxx",
"name": "Loja do Joao",
"username": "joao",
"is_live": true,
"created_at": "2025-06-01T00:00:00.000Z"
}
Rate limits
A API aplica limites de requisições para garantir estabilidade e proteger contra abusos.
| Endpoint | Limite | Escopo |
|---|---|---|
| POST /api/checkouts | 30 / min | por IP |
| GET /api/checkout-page/:id | 30 / min | por IP (público) |
| GET /api/pay/:id | 60 / min | por IP (público) |
| POST /api/pay/:id/simulate | 5 / min | por IP (público, só sandbox) |
| POST /api/merchants/:username/checkout | 10 / min | por IP (público) |
| POST /api/products/:id/checkout | 10 / min | por IP (público) |
| GET /api/products/:id/public | 30 / min | por IP (público) |
| GET /api/merchants/:username/public | 30 / min | por IP (público) |
| Por merchant (API key) | Configurável | aplicado após auth, em cima do limite por IP |
- Para requests autenticados com API key, um rate limit adicional é aplicado por merchant (configurável — fale com o suporte se precisar de aumento).
- Para endpoints públicos (sem auth), o rate limit é aplicado apenas por IP.
- Quando o limite é atingido, a API retorna status
429com a mensagem"Muitas requisições. Tente novamente em 1 minuto."— aguarde ~60s antes de tentar de novo.