Para automatizar cobranças no n8n com o HeroPay, você usa dois nodes nativos: um HTTP Request que chama POST /payment_links e devolve a URL de um checkout com Pix, cartão em até 12x e boleto, e um Webhook que recebe o evento spark_payment_confirmed quando o cliente paga. No meio, o n8n faz o que já faz bem: manda o link por WhatsApp ou e-mail e atualiza a planilha ou o CRM sozinho. Do workflow vazio à primeira cobrança paga no sandbox em minutos, com Pix a R$ 0 por transação.
Criar conta sandbox Ler a documentação
O essencial em 60 segundos
- O HeroPay se integra ao n8n pelos nodes nativos HTTP Request (criar cobrança) e Webhook (receber pagamento). Funciona no n8n Cloud e no self-hosted, sem instalar nada.
- Não existe node oficial do HeroPay no n8n, e o caminho desta página não depende de um: é a API REST chamada pelos nodes genéricos, com um workflow pronto para importar.
- Uma chamada
POST /payment_linksdevolve emdata.offer.urlum checkout com Pix, cartão e boleto. Você manda esse link por WhatsApp, e-mail ou SMS. - Pix custa R$ 0 e boleto R$ 0 por transação; cartão, 3,49% por transação aprovada, em até 12x. Sem mensalidade, ativação ou mínimo (preços).
- O campo
srcdo link volta emcart.srcem todo webhook daquela compra. Mande o id do pedido nele e o n8n sabe qual linha da planilha dar baixa. - O webhook chega assinado no header
X-HeroPay-Signature(HMAC SHA-256 do corpo bruto). Ligue Raw Body no node Webhook e valide num Code node antes de dar baixa. - O token fica numa credencial do n8n (Header Auth), nunca digitado no node: assim ele não vai junto quando você exporta ou compartilha o workflow.
Como automatizar cobranças no n8n com o HeroPay?
O fluxo tem duas metades, e o workflow pronto mais abaixo já traz as duas:
- Cobrar: um gatilho (formulário, linha nova na planilha, deal ganho no CRM) cria o pedido, chama
POST /payment_linkse manda o link ao cliente. - Dar baixa: o HeroPay avisa o pagamento no seu Webhook do n8n, o workflow valida a assinatura, confere o valor e marca o pedido como pago.
Entre as duas metades não há polling. Confie no evento, não na consulta de minuto em minuto.
Passo 1: crie a conta sandbox e a credencial no n8n
Crie a conta em app.heropay.tech e copie o token de API (um Bearer JWT) nas configurações. No sandbox a URL base é https://api.beta.heropay.tech; em produção, https://api.heropay.tech.
No n8n, abra Credentials > Add credential > Header Auth e preencha:
| Campo da credencial | Valor |
|---|---|
| Name (nome da credencial) | HeroPay Sandbox |
| Name (nome do header) | Authorization |
| Value | Bearer SEU_TOKEN_DO_SANDBOX |
Repare no Bearer com espaço antes do token. Esquecer o prefixo é a causa número um de 401.
Screenshot descrito: tela de credencial Header Auth do n8n com Name "Authorization" e Value mascarado começando por "Bearer".
Passo 2: configure o node HTTP Request que cria o link
Adicione um node HTTP Request e preencha exatamente assim:
| Campo do node | Valor |
|---|---|
| Method | POST |
| URL | https://api.beta.heropay.tech/payment_links |
| Authentication | Generic Credential Type |
| Generic Auth Type | Header Auth, credencial HeroPay Sandbox |
| Send Headers | ligado, Accept = application/vnd.herospark.com; version=1 |
| Send Body | ligado |
| Body Content Type | JSON |
| Specify Body | Using JSON |
No campo JSON, cole (em modo expressão):
{{ JSON.stringify({
payment_link: {
name: $json.produto,
description: 'Pedido ' + $json.pedido_id,
price_cents: $json.valor_cents, // 19700 = R$ 197,00
absorbs_fees: true,
max_installments: $json.max_installments,
payment_methods: ['pix', 'credit_card', 'bank_slip'],
src: $json.pedido_id // volta em cart.src no webhook
}
}) }}Atalho: o HTTP Request tem Import cURL. Cole o curl da documentação de desenvolvedores e o n8n preenche método, URL, headers e corpo; depois troque o header Authorization digitado pela credencial.
A resposta 201 traz o que importa para o resto do workflow:
{
"message": "Payment link created successfully",
"data": {
"id": 136,
"name": "Consulta de avaliação",
"price_cents": 19700,
"max_installments": 12,
"period": "unitary",
"offer": {
"id": 6094,
"kind": "payment_link",
"url": "https://<checkout>/fc280e25-...-6094?src=ped-muekq3dw-9a81",
"accepted_payment_methods": ["pix", "credit_card", "bank_slip"]
}
}
}Nos nodes seguintes, o link é {{ $json.data.offer.url }} e o id do link é {{ $json.data.id }}.
Passo 3: mande o link por WhatsApp ou e-mail
Com a URL em mãos, qualquer node de mensagem serve:
- WhatsApp Business Cloud (node nativo, API oficial da Meta): operação
Send,Recipient's Phone Numbercom DDI (5511988887777) e o link no texto. Fora da janela de 24 horas desde a última mensagem do cliente, a Meta exige mensagem de modelo aprovada: use a operaçãoSend Template. - Gmail, Outlook ou SMTP (nativos): assunto com o nome do produto e o link no corpo.
- Evolution API ou outro node da comunidade: funciona igual, trocando só o node de envio. É integração não oficial com o WhatsApp; avalie o risco antes de usar em produção.
O HeroPay não devolve o Pix copia e cola pela API: o link abre o checkout, onde o cliente escolhe Pix (com QR Code e copia e cola), cartão ou boleto. Na prática, para cobrança por WhatsApp isso é vantagem, porque o mesmo link aceita os três métodos e o parcelamento.
Passo 4: receba o pagamento no node Webhook
Adicione um node Webhook:
| Campo do node | Valor |
|---|---|
| HTTP Method | POST |
| Path | heropay/pagamento-confirmado |
| Authentication | None (quem autentica é a assinatura HMAC do passo 5) |
| Respond | Using 'Respond to Webhook' Node |
| Options > Raw Body | ligado |
Copie a Production URL (a que tem /webhook/, não /webhook-test/) e registre no HeroPay. Pode ser por curl ou por um node HTTP Request que você roda uma vez (o workflow pronto traz esse ramo):
curl -X POST "https://api.beta.heropay.tech/webhook" \
-H "Authorization: Bearer $HEROPAY_JWT_TOKEN" \
-H "Accept: application/vnd.herospark.com; version=1" \
-H "Content-Type: application/json" \
-d '{
"webhook": {
"trigger": "spark_payment_confirmed",
"webhook_url": "https://SEU-N8N/webhook/heropay/pagamento-confirmado",
"request_method": "post"
}
}'Cada webhook cadastrado ouve um gatilho. No n8n, use um Path por gatilho: heropay/pagamento-confirmado, heropay/estorno, heropay/pix-gerado. Cada caminho vira um ramo do workflow e você nunca confunde um estorno com um pagamento. A lista dos 9 gatilhos está em webhooks.
Passo 5: valide a assinatura num Code node
Com Raw Body ligado, o n8n guarda os bytes exatos da requisição na propriedade binária data. É sobre esses bytes que o HMAC é calculado: se você assinar o JSON já interpretado, a reserialização muda espaços e ordem de chaves e a assinatura nunca bate.
Adicione um Code node (JavaScript, Run Once for All Items) logo depois do Webhook:
const crypto = require('crypto');
// segredo fora do workflow: Variables do n8n ou variável de ambiente
let segredo;
try { segredo = $vars.HEROPAY_WEBHOOK_SECRET; } catch (e) {}
if (!segredo) { try { segredo = $env.HEROPAY_WEBHOOK_SECRET; } catch (e) {} }
if (!segredo) throw new Error('Defina HEROPAY_WEBHOOK_SECRET em Variables ou no ambiente do n8n');
const headers = $input.first().json.headers || {};
const recebida = String(headers['x-heropay-signature'] || '').trim();
// bytes exatos que chegaram (opção Raw Body ligada no Webhook)
const bruto = await this.helpers.getBinaryDataBuffer(0, 'data');
const hmac = () => crypto.createHmac('sha256', segredo).update(bruto);
// confira o formato da assinatura (hex ou base64) na documentação e mantenha só o certo
const esperadas = [hmac().digest('hex'), hmac().digest('base64')];
const a = Buffer.from(recebida);
const valida = esperadas.some((e) => {
const b = Buffer.from(e);
return a.length === b.length && crypto.timingSafeEqual(a, b);
});
if (!valida) return [{ json: { valida: false } }];
const evento = JSON.parse(bruto.toString('utf8'));
// nomes e formato dos campos de payment: confira no exemplo de payload da documentação
const VALOR_EM_REAIS = true; // true se payment.value vier em reais ("97.00"); false se vier em centavos
const valor = parseFloat(evento.payment.value);
return [{ json: {
valida: true,
pedido_id: evento.cart && evento.cart.src,
payment_id: String(evento.payment.id),
metodo: evento.payment.method,
valor_cents: VALOR_EM_REAIS ? Math.round(valor * 100) : Math.round(valor),
pago_em: evento.payment.date,
chave_dedupe: 'spark_payment_confirmed:' + evento.payment.id
} }];Três detalhes que fazem esse node funcionar:
cryptoestá liberado no n8n Cloud. No self-hosted, libere com a variável de ambienteNODE_FUNCTION_ALLOW_BUILTIN=cryptono container do n8n.- O segredo não fica no código. Use Variables (
$vars) ou uma variável de ambiente ($env). Se a sua instância bloqueia$envnos nodes e o seu plano não tem Variables, o último recurso é um node de configuração separado que você nunca exporta. - Comparação em tempo constante (
timingSafeEqual), nunca===, para não vazar pelo tempo de resposta quantos caracteres da assinatura estão certos.
Depois do Code node, um IF em {{ $json.valida }}: verdadeiro segue para Respond to Webhook com 200; falso vai para outro Respond to Webhook com 401. Responder logo depois de validar, antes de planilha e e-mail, evita timeout e retentativa desnecessária.
Se o primeiro evento real do sandbox for reprovado, confira o segredo, a opção Raw Body e o formato da assinatura antes de qualquer outra coisa. Nunca desligue a validação para "destravar" o fluxo: sem ela, qualquer um que descubra a URL marca pedidos como pagos.
Passo 6: dê baixa sem processar duas vezes
Depois do 200, o workflow busca o pedido pelo pedido_id (que veio de cart.src) e só marca como pago se duas condições baterem:
- o status ainda é
pending(o mesmo evento pode chegar duas vezes por causa da retentativa; se já estápaid, não faz nada); - o valor pago, em centavos, é igual ao valor do pedido.
Só então atualiza a linha para paid, grava payment_id, método e data, e manda a confirmação ao cliente. Teste no sandbox: preencha o formulário, faça uma compra de teste pelo link e veja a linha mudar sozinha.
Passo 7: troque a chave e vá pro ar
Crie a credencial HeroPay Produção com o token de produção, troque a URL base dos nodes HTTP Request para https://api.heropay.tech, troque o segredo em HEROPAY_WEBHOOK_SECRET e registre o webhook de novo na API de produção. Ative (publique) o workflow: a Production URL do Webhook só escuta com o workflow ativo. Sem fila de homologação: o que passou no sandbox é o que roda em produção.
O workflow pronto para importar
Copie o JSON abaixo, abra um workflow vazio no n8n e cole com Ctrl+V (ou use Import from File). Ele traz três ramos:
| Ramo | Nodes | O que faz |
|---|---|---|
| Cobrar | Form Trigger > Code > HTTP Request > Google Sheets > WhatsApp e Gmail | Formulário interno cria o pedido, gera o link, registra na planilha e envia ao cliente |
| Dar baixa | Webhook > Code (HMAC) > IF > Respond to Webhook > Google Sheets > IF > Google Sheets > Gmail | Valida o evento, responde 200, confere pendência e valor, marca pago e confirma |
| Configurar | Manual Trigger > HTTP Request | Rode uma vez para registrar o webhook no HeroPay |
Antes de rodar: crie a aba pedidos na planilha com as colunas pedido_id, nome, email, whatsapp, produto, valor_cents, status, checkout_url, heropay_link_id, payment_id, metodo, pago_em, criado_em; troque SUA_PLANILHA, SEU_PHONE_NUMBER_ID e SEU-N8N; e selecione as suas credenciais em cada node (no JSON elas aparecem só como nome, sem segredo).
{
"name": "HeroPay: cobrança com Pix, WhatsApp e planilha",
"nodes": [
{
"parameters": {
"authentication": "basicAuth",
"formTitle": "Nova cobrança HeroPay",
"formDescription": "Preencha para gerar o link de pagamento e enviar ao cliente.",
"formFields": {
"values": [
{ "fieldLabel": "Nome do cliente", "requiredField": true },
{ "fieldLabel": "E-mail", "fieldType": "email", "requiredField": true },
{ "fieldLabel": "WhatsApp com DDD", "requiredField": true },
{ "fieldLabel": "Produto ou serviço", "requiredField": true },
{ "fieldLabel": "Valor em reais", "fieldType": "number", "requiredField": true }
]
},
"options": {}
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000001",
"name": "Formulário de cobrança",
"type": "n8n-nodes-base.formTrigger",
"typeVersion": 2.2,
"position": [0, 0],
"webhookId": "0b7e6f0a-1a11-4c01-9a01-0000000000f1",
"credentials": {
"httpBasicAuth": { "id": "SUBSTITUA", "name": "Acesso ao formulário" }
}
},
{
"parameters": {
"jsCode": "const f = $input.first().json;\nconst priceCents = Math.round(Number(f['Valor em reais']) * 100);\nif (!Number.isInteger(priceCents) || priceCents < 500) {\n throw new Error('Valor mínimo da cobrança: R$ 5,00');\n}\n// parcela mínima de R$ 1,99 no cartão\nconst maxInstallments = Math.max(1, Math.min(12, Math.floor(priceCents / 199)));\n// src aceita só letras, números e . _ - ~\nconst pedidoId = 'ped-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 6);\nlet whatsapp = String(f['WhatsApp com DDD']).replace(/\\D/g, '');\nif (!whatsapp.startsWith('55')) whatsapp = '55' + whatsapp;\nreturn [{ json: {\n pedido_id: pedidoId,\n nome: f['Nome do cliente'],\n email: f['E-mail'],\n whatsapp,\n produto: f['Produto ou serviço'],\n valor_cents: priceCents,\n max_installments: maxInstallments,\n criado_em: new Date().toISOString()\n} }];"
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000002",
"name": "Preparar pedido",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [220, 0]
},
{
"parameters": {
"method": "POST",
"url": "https://api.beta.heropay.tech/payment_links",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{ "name": "Accept", "value": "application/vnd.herospark.com; version=1" }
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ payment_link: { name: $json.produto, description: 'Pedido ' + $json.pedido_id, price_cents: $json.valor_cents, absorbs_fees: true, max_installments: $json.max_installments, payment_methods: ['pix', 'credit_card', 'bank_slip'], src: $json.pedido_id } }) }}",
"options": {}
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000003",
"name": "HeroPay: criar link",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [440, 0],
"credentials": {
"httpHeaderAuth": { "id": "SUBSTITUA", "name": "HeroPay Sandbox" }
}
},
{
"parameters": {
"operation": "append",
"documentId": { "__rl": true, "mode": "url", "value": "https://docs.google.com/spreadsheets/d/SUA_PLANILHA" },
"sheetName": { "__rl": true, "mode": "name", "value": "pedidos" },
"columns": {
"mappingMode": "defineBelow",
"value": {
"pedido_id": "={{ $('Preparar pedido').item.json.pedido_id }}",
"nome": "={{ $('Preparar pedido').item.json.nome }}",
"email": "={{ $('Preparar pedido').item.json.email }}",
"whatsapp": "={{ $('Preparar pedido').item.json.whatsapp }}",
"produto": "={{ $('Preparar pedido').item.json.produto }}",
"valor_cents": "={{ $('Preparar pedido').item.json.valor_cents }}",
"status": "pending",
"checkout_url": "={{ $json.data.offer.url }}",
"heropay_link_id": "={{ $json.data.id }}",
"criado_em": "={{ $('Preparar pedido').item.json.criado_em }}"
},
"matchingColumns": [],
"schema": []
},
"options": {}
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000004",
"name": "Planilha: registrar pedido",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [660, 0],
"credentials": {
"googleSheetsOAuth2Api": { "id": "SUBSTITUA", "name": "Google Sheets" }
}
},
{
"parameters": {
"operation": "send",
"phoneNumberId": "SEU_PHONE_NUMBER_ID",
"recipientPhoneNumber": "={{ $('Preparar pedido').item.json.whatsapp }}",
"textBody": "=Olá, {{ $('Preparar pedido').item.json.nome }}! Segue o link para pagar {{ $('Preparar pedido').item.json.produto }} com Pix, cartão ou boleto: {{ $('HeroPay: criar link').item.json.data.offer.url }}",
"additionalFields": {}
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000005",
"name": "WhatsApp: enviar link",
"type": "n8n-nodes-base.whatsApp",
"typeVersion": 1,
"position": [880, -100],
"credentials": {
"whatsAppApi": { "id": "SUBSTITUA", "name": "WhatsApp Business Cloud" }
}
},
{
"parameters": {
"sendTo": "={{ $('Preparar pedido').item.json.email }}",
"subject": "=Seu link de pagamento: {{ $('Preparar pedido').item.json.produto }}",
"emailType": "text",
"message": "=Olá, {{ $('Preparar pedido').item.json.nome }}.\n\nPague com Pix, cartão em até {{ $('Preparar pedido').item.json.max_installments }}x ou boleto neste link:\n{{ $('HeroPay: criar link').item.json.data.offer.url }}",
"options": {}
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000006",
"name": "E-mail: enviar link",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [880, 100],
"credentials": {
"gmailOAuth2": { "id": "SUBSTITUA", "name": "Gmail" }
}
},
{
"parameters": {
"httpMethod": "POST",
"path": "heropay/pagamento-confirmado",
"responseMode": "responseNode",
"options": { "rawBody": true }
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000007",
"name": "HeroPay: pagamento confirmado",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [0, 400],
"webhookId": "0b7e6f0a-1a11-4c01-9a01-0000000000f7"
},
{
"parameters": {
"jsCode": "const crypto = require('crypto');\n\n// segredo fora do workflow: Variables do n8n ou variável de ambiente\nlet segredo;\ntry { segredo = $vars.HEROPAY_WEBHOOK_SECRET; } catch (e) {}\nif (!segredo) { try { segredo = $env.HEROPAY_WEBHOOK_SECRET; } catch (e) {} }\nif (!segredo) throw new Error('Defina HEROPAY_WEBHOOK_SECRET em Variables ou no ambiente do n8n');\n\nconst headers = $input.first().json.headers || {};\nconst recebida = String(headers['x-heropay-signature'] || '').trim();\n\n// bytes exatos que chegaram (opção Raw Body ligada no Webhook)\nconst bruto = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst hmac = () => crypto.createHmac('sha256', segredo).update(bruto);\n// confira o formato da assinatura (hex ou base64) na documentação e mantenha só o certo\nconst esperadas = [hmac().digest('hex'), hmac().digest('base64')];\n\nconst a = Buffer.from(recebida);\nconst valida = esperadas.some((e) => {\n const b = Buffer.from(e);\n return a.length === b.length && crypto.timingSafeEqual(a, b);\n});\n\nif (!valida) return [{ json: { valida: false } }];\n\nconst evento = JSON.parse(bruto.toString('utf8'));\n// nomes e formato dos campos de payment: confira no exemplo de payload da documentação\nconst VALOR_EM_REAIS = true; // true se payment.value vier em reais (\"97.00\"); false se vier em centavos\nconst valor = parseFloat(evento.payment.value);\nreturn [{ json: {\n valida: true,\n pedido_id: evento.cart && evento.cart.src,\n payment_id: String(evento.payment.id),\n metodo: evento.payment.method,\n valor_cents: VALOR_EM_REAIS ? Math.round(valor * 100) : Math.round(valor),\n pago_em: evento.payment.date,\n chave_dedupe: 'spark_payment_confirmed:' + evento.payment.id\n} }];"
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000008",
"name": "Validar assinatura HMAC",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [220, 400]
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
"conditions": [
{
"id": "c1a1b1c1-0000-4000-8000-000000000001",
"leftValue": "={{ $json.valida }}",
"rightValue": true,
"operator": { "type": "boolean", "operation": "true", "singleValue": true }
}
],
"combinator": "and"
},
"options": {}
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000009",
"name": "Assinatura válida?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [440, 400]
},
{
"parameters": {
"respondWith": "noData",
"options": { "responseCode": 200 }
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000010",
"name": "Responder 200",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [660, 300]
},
{
"parameters": {
"respondWith": "noData",
"options": { "responseCode": 401 }
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000011",
"name": "Responder 401",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [660, 500]
},
{
"parameters": {
"operation": "read",
"documentId": { "__rl": true, "mode": "url", "value": "https://docs.google.com/spreadsheets/d/SUA_PLANILHA" },
"sheetName": { "__rl": true, "mode": "name", "value": "pedidos" },
"filtersUI": {
"values": [
{ "lookupColumn": "pedido_id", "lookupValue": "={{ $('Validar assinatura HMAC').item.json.pedido_id }}" }
]
},
"options": {}
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000012",
"name": "Planilha: buscar pedido",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [880, 300],
"alwaysOutputData": false,
"credentials": {
"googleSheetsOAuth2Api": { "id": "SUBSTITUA", "name": "Google Sheets" }
}
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" },
"conditions": [
{
"id": "c1a1b1c1-0000-4000-8000-000000000002",
"leftValue": "={{ $json.status }}",
"rightValue": "pending",
"operator": { "type": "string", "operation": "equals" }
},
{
"id": "c1a1b1c1-0000-4000-8000-000000000003",
"leftValue": "={{ Number($json.valor_cents) }}",
"rightValue": "={{ $('Validar assinatura HMAC').item.json.valor_cents }}",
"operator": { "type": "number", "operation": "equals" }
}
],
"combinator": "and"
},
"options": {}
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000013",
"name": "Pendente e valor confere?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [1100, 300]
},
{
"parameters": {
"operation": "update",
"documentId": { "__rl": true, "mode": "url", "value": "https://docs.google.com/spreadsheets/d/SUA_PLANILHA" },
"sheetName": { "__rl": true, "mode": "name", "value": "pedidos" },
"columns": {
"mappingMode": "defineBelow",
"value": {
"pedido_id": "={{ $('Validar assinatura HMAC').item.json.pedido_id }}",
"status": "paid",
"payment_id": "={{ $('Validar assinatura HMAC').item.json.payment_id }}",
"metodo": "={{ $('Validar assinatura HMAC').item.json.metodo }}",
"pago_em": "={{ $('Validar assinatura HMAC').item.json.pago_em }}"
},
"matchingColumns": ["pedido_id"],
"schema": []
},
"options": {}
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000014",
"name": "Planilha: marcar pago",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [1320, 200],
"credentials": {
"googleSheetsOAuth2Api": { "id": "SUBSTITUA", "name": "Google Sheets" }
}
},
{
"parameters": {
"sendTo": "={{ $('Planilha: buscar pedido').item.json.email }}",
"subject": "=Pagamento confirmado: {{ $('Planilha: buscar pedido').item.json.produto }}",
"emailType": "text",
"message": "=Olá, {{ $('Planilha: buscar pedido').item.json.nome }}. Recebemos seu pagamento. Obrigado!",
"options": {}
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000015",
"name": "E-mail: confirmar pagamento",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [1540, 200],
"credentials": {
"gmailOAuth2": { "id": "SUBSTITUA", "name": "Gmail" }
}
},
{
"parameters": {},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000016",
"name": "Rodar uma vez: registrar webhook",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [0, 800]
},
{
"parameters": {
"method": "POST",
"url": "https://api.beta.heropay.tech/webhook",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{ "name": "Accept", "value": "application/vnd.herospark.com; version=1" }
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\n \"webhook\": {\n \"trigger\": \"spark_payment_confirmed\",\n \"webhook_url\": \"https://SEU-N8N/webhook/heropay/pagamento-confirmado\",\n \"request_method\": \"post\"\n }\n}",
"options": {}
},
"id": "0b7e6f0a-1a11-4c01-9a01-000000000017",
"name": "HeroPay: registrar webhook",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [220, 800],
"credentials": {
"httpHeaderAuth": { "id": "SUBSTITUA", "name": "HeroPay Sandbox" }
}
}
],
"connections": {
"Formulário de cobrança": { "main": [[{ "node": "Preparar pedido", "type": "main", "index": 0 }]] },
"Preparar pedido": { "main": [[{ "node": "HeroPay: criar link", "type": "main", "index": 0 }]] },
"HeroPay: criar link": { "main": [[{ "node": "Planilha: registrar pedido", "type": "main", "index": 0 }]] },
"Planilha: registrar pedido": {
"main": [[
{ "node": "WhatsApp: enviar link", "type": "main", "index": 0 },
{ "node": "E-mail: enviar link", "type": "main", "index": 0 }
]]
},
"HeroPay: pagamento confirmado": { "main": [[{ "node": "Validar assinatura HMAC", "type": "main", "index": 0 }]] },
"Validar assinatura HMAC": { "main": [[{ "node": "Assinatura válida?", "type": "main", "index": 0 }]] },
"Assinatura válida?": {
"main": [
[{ "node": "Responder 200", "type": "main", "index": 0 }],
[{ "node": "Responder 401", "type": "main", "index": 0 }]
]
},
"Responder 200": { "main": [[{ "node": "Planilha: buscar pedido", "type": "main", "index": 0 }]] },
"Planilha: buscar pedido": { "main": [[{ "node": "Pendente e valor confere?", "type": "main", "index": 0 }]] },
"Pendente e valor confere?": { "main": [[{ "node": "Planilha: marcar pago", "type": "main", "index": 0 }], []] },
"Planilha: marcar pago": { "main": [[{ "node": "E-mail: confirmar pagamento", "type": "main", "index": 0 }]] },
"Rodar uma vez: registrar webhook": { "main": [[{ "node": "HeroPay: registrar webhook", "type": "main", "index": 0 }]] }
},
"settings": { "executionOrder": "v1", "saveDataSuccessExecution": "none" },
"pinData": {}
}O formulário é interno (quem preenche é você ou o time, por isso ele pede senha via Basic Auth). Para cobrar a partir de uma planilha, troque o Form Trigger por um Google Sheets Trigger em "Row Added"; para cobrar quando um negócio é ganho, use o trigger do seu CRM (HubSpot, Pipedrive, RD Station) e mantenha o resto igual.
O prompt pronto para adaptar o workflow
Precisa de outro gatilho, outro CRM ou de mais gatilhos do HeroPay? Cole o JSON acima e este prompt no Claude, no ChatGPT ou no construtor de workflows com IA do próprio n8n. Troque só o que está entre colchetes.
Você vai adaptar um workflow do n8n que cobra clientes pela API HeroPay
(referência: https://heropay.tech/llms.txt e https://docs.heropay.tech). Use SOMENTE nodes nativos do n8n
e devolva o JSON completo do workflow, pronto para colar no editor.
CONTEXTO
- Gatilho da cobrança: [ex.: "linha nova na aba 'cobrancas' do Google Sheets" ou "deal ganho no Pipedrive"]
- Onde envio o link: [WhatsApp Business Cloud / Gmail / os dois]
- Onde dou baixa: [Google Sheets / Pipedrive / HubSpot / Postgres]
- Eventos que quero tratar: spark_payment_confirmed, [refunded, chargeback_request, payment_credit_cart_refused]
REGRAS DA API (não invente nada fora disto)
1. Criar cobrança: HTTP Request POST {URL_BASE}/payment_links.
URL_BASE no sandbox: https://api.beta.heropay.tech ; produção: https://api.heropay.tech
Autenticação: Generic Credential Type > Header Auth, credencial chamada "HeroPay Sandbox"
(Authorization: Bearer <token>). NUNCA escreva o token em header digitado no node.
Header extra: Accept: application/vnd.herospark.com; version=1
Corpo JSON: { "payment_link": { "name", "description", "price_cents" (inteiro em centavos,
mínimo 500), "absorbs_fees": true, "max_installments" (1 a 12, parcela mínima de 199 centavos),
"payment_methods": ["pix","credit_card","bank_slip"], "src": <id do pedido> } }
"src" aceita só letras, números e . _ - ~ (máx. 255).
O link do checkout vem em data.offer.url. Não ligue "Retry On Fail" neste node:
repetir um POST pode criar dois links.
2. Receber eventos: um node Webhook POR GATILHO, cada um com Path próprio
(heropay/pagamento-confirmado, heropay/estorno ...), Respond = "Using 'Respond to Webhook' Node",
Options > Raw Body ligado. Use o Path para saber qual gatilho chegou.
3. Validar cada evento num Code node: HMAC SHA-256 do corpo bruto (hex ou base64, conforme a documentação)
(this.helpers.getBinaryDataBuffer(0, 'data')) com o segredo lido de $vars.HEROPAY_WEBHOOK_SECRET
(ou $env), comparado com o header x-heropay-signature via crypto.timingSafeEqual.
Inválido: Respond to Webhook 401. Válido: Respond to Webhook 200 e só depois o resto.
4. O id do pedido volta em cart.src. Confira na documentação o formato de payment.value
(reais ou centavos) e compare sempre em centavos.
5. Idempotência: só marque como pago se o pedido ainda estiver pendente e o valor bater.
Chave de deduplicação: gatilho + payment.id.
6. Estorno ou chargeback: marque o pedido como estornado e avise [quem] por [canal].
ENTREGA
Devolva o JSON do workflow e, em seguida, a lista de campos que eu preciso trocar
(planilha, IDs, URL do n8n) e as credenciais que preciso criar. Não crie nada além do pedido.O que o workflow faz por baixo?
| Peça | Node do n8n | Endpoint ou evento HeroPay |
|---|---|---|
| Criar a cobrança | HTTP Request (Header Auth) | POST /payment_links |
| Registrar o webhook | HTTP Request (rodado uma vez) | POST /webhook |
| Receber o pagamento | Webhook com Raw Body | spark_payment_confirmed |
| Autenticar o evento | Code (crypto) | header X-HeroPay-Signature |
| Casar evento e pedido | Google Sheets (Get Row(s)) | cart.src = pedido_id |
| Tratar estorno (opcional) | Webhook com outro Path | refunded, chargeback_request |
| Conciliar no fim do dia (opcional) | Schedule Trigger + HTTP Request | GET /sales/unitary |
O payload segue o mesmo template para todos os gatilhos: buyer (nome, e-mail, telefone, endereço, documento), payment (ID, data, valor, método e status), offer, product, cart.src, subscription, installments e payment_methods. Os nomes exatos de cada campo estão no exemplo de payload da documentação; confira antes de mapear no n8n. No evento de recusa (payment_credit_cart_refused, com cart), o motivo da operadora vem em payment_methods.credit_card.refused_message: dá para mandar no WhatsApp "o banco recusou por limite, tente parcelar ou pagar no Pix" em vez de um "recusado" seco. Detalhes em webhooks.
Quais são as armadilhas mais comuns ao cobrar pelo n8n?
Token digitado no node e vazado no workflow compartilhado
Se o Authorization: Bearer ... foi escrito direto no campo de header do HTTP Request, ele vai junto no JSON quando você exporta, cola num fórum, publica um template ou manda para um freelancer. Com o token, alguém cria links, lê suas vendas e consulta seu saldo. Credenciais do n8n não viajam no export: o JSON leva só o nome e um id da credencial. Regra: token só em credencial Header Auth, segredo do webhook só em Variables ou variável de ambiente. Antes de compartilhar, procure por Bearer e por SECRET no JSON exportado. Se vazou, regenere o token no painel na hora.
Registrar a Test URL em vez da Production URL
A Test URL (/webhook-test/...) só escuta enquanto você clica em "Listen for test event" no editor. Registrada no HeroPay, ela funciona uma vez no teste e falha para sempre depois. Registre a Production URL (/webhook/...) e ative o workflow. Se o evento "sumiu", olhe Executions: execuções de produção aparecem lá, não no canvas.
Validar HMAC sem Raw Body
Sem a opção Raw Body, o node Webhook entrega o JSON já interpretado, e qualquer tentativa de recalcular a assinatura a partir de JSON.stringify($json.body) falha, porque espaços e ordem de chaves mudam. É a dúvida mais repetida no fórum do n8n sobre webhooks assinados. Ligue Raw Body e assine os bytes de getBinaryDataBuffer(0, 'data').
Processar o mesmo pagamento duas vezes
A entrega é "pelo menos uma vez": se o seu 200 se perdeu ou demorou, o HeroPay retenta e o evento chega de novo. O IF "pendente e valor confere?" do workflow resolve o caso comum. Em volume alto, planilha não é trava: duas execuções simultâneas podem ler pending ao mesmo tempo. Aí troque a checagem por um node Postgres ou MySQL com INSERT da chave_dedupe numa tabela com índice único: se o insert falhar por duplicidade, pare o ramo. Veja idempotência.
Ligar "Retry On Fail" no node que cria o link
Retentar é ótimo para leitura. No POST /payment_links é perigoso: se a primeira chamada criou o link e só a resposta se perdeu, a retentativa cria um segundo link para o mesmo pedido. A API v1 não tem header Idempotency-Key. Deixe o retry desligado nesse node e, em caso de erro, confira GET /payment_links antes de tentar de novo.
Reais onde a API espera centavos
price_cents é inteiro em centavos: R$ 197,00 é 19700. Mandar 197 cria uma cobrança de R$ 1,97, abaixo do mínimo de R$ 5,00, e a API responde 422. O Code node do workflow converte e bloqueia valor abaixo do mínimo. Ele também limita as parcelas: cada parcela precisa ter pelo menos R$ 1,99, então uma cobrança de R$ 10 vai no máximo em 5x.
Dar baixa pelo "voltei do checkout"
Página de obrigado não prova pagamento: o Pix pode ser pago minutos depois no celular, e o boleto, dias depois. A única fonte de verdade é o spark_payment_confirmed no Webhook. Confie no evento, não no redirect.
Guardar dados pessoais em todas as execuções
Cada execução salva o payload, com nome, e-mail, telefone e documento do comprador. Para ficar em paz com a LGPD, o workflow pronto vem com Save successful executions desligado; mantenha as falhas salvas para depurar e configure a limpeza automática de execuções na sua instância.
Quanto custa cobrar pelo n8n com Pix?
O n8n não cobra nada a mais por chamar uma API: você paga o seu plano do n8n (ou o servidor, no self-hosted) e as tarifas do gateway. É aí que o Pix muda a conta. Cenário: 200 cobranças por mês de R$ 150 em Pix (mensalidade de academia, consulta, aula particular):
| 200 cobranças Pix de R$ 150 por mês | HeroPay | AbacatePay | Asaas | Stripe BR |
|---|---|---|---|---|
| Tarifa por Pix | R$ 0 | R$ 0,80 | R$ 1,99 (R$ 0,99 nos 3 primeiros meses) | 1,19% (só por convite) |
| Custo no mês | R$ 0 | R$ 160,00 | R$ 398,00 | R$ 357,00 |
| Custo em 12 meses | R$ 0 | R$ 1.920,00 | R$ 4.176,00 no 1º ano | R$ 4.284,00 |
| Node oficial no n8n | Não (HTTP Request + Webhook) | Não encontrado | Sim, @asaasbr/n8n-nodes-asaas | Sim, node Stripe nativo |
Verificado em 23/set/2026. Fontes: heropay.tech/precos, abacatepay.com, asaas.com/precos-e-taxas, stripe.com/br/pricing, docs.asaas.com/docs/n8n.
Honestidade no cartão: à vista, acima de R$ 98 por cobrança, o Asaas sai mais barato. Numa cobrança de R$ 150 no cartão à vista, o HeroPay cobra 3,49% (R$ 5,24) e o Asaas, 2,99% + R$ 0,49 (R$ 4,98). Até R$ 98, o HeroPay cobra o mesmo ou menos. A vantagem do HeroPay está no Pix e no boleto a R$ 0, no checkout que aprova mais (93%+ no cartão, com Parcelamento Inteligente e 2 cartões) e no webhook assinado com HMAC. Se a sua cobrança é quase toda Pix, a conta acima decide. Veja a comparação completa em Asaas vs HeroPay.
Dá para cobrar sem montar workflow nenhum?
Dá. Se você só precisa mandar cobrança avulsa por WhatsApp:
- No painel, crie um link de pagamento com nome, preço e métodos.
- Mande o link manualmente, ou num node de WhatsApp com o link fixo no texto.
- Acrescente
?src=nome-do-clientena URL para saber quem pagou quando o webhook ou o relatório chegar.
O limite desse caminho é a baixa: sem o Webhook, você confere os pagamentos no painel. Quando o volume crescer, importe o workflow desta página e deixe o n8n trabalhar.
Perguntas frequentes
Existe node oficial do HeroPay no n8n?
Não. A integração usa dois nodes nativos: HTTP Request para criar a cobrança com POST /payment_links e Webhook para receber o evento de pagamento. Na prática, isso não limita nada: a API é REST com JSON e dois headers, e o workflow pronto desta página já traz os nodes configurados, a validação HMAC e a baixa na planilha. Asaas e Efí têm nodes publicados; com o HeroPay, o HTTP Request fala direto com a API, sem camada extra para atualizar.
Como gerar cobrança Pix no n8n?
Com um node HTTP Request em POST https://api.beta.heropay.tech/payment_links (sandbox), autenticação por credencial Header Auth com Authorization: Bearer <token>, header Accept: application/vnd.herospark.com; version=1 e corpo JSON com price_cents, payment_methods: ["pix"] e src. A resposta traz em data.offer.url o checkout com QR Code e Pix copia e cola. O HeroPay não devolve o copia e cola solto pela API: o cliente abre o link e paga. Pix custa R$ 0 por transação.
Como mandar cobrança por WhatsApp no n8n?
Crie o link com o HTTP Request e passe {{ $json.data.offer.url }} para um node de WhatsApp. O nativo é o WhatsApp Business Cloud, que usa a API oficial da Meta: operação Send com o número no formato 5511988887777. Fora da janela de 24 horas da última mensagem do cliente, a Meta exige modelo aprovado, então use Send Template para a primeira cobrança. Nodes da comunidade como Evolution API também funcionam, trocando só o node de envio, mas são integrações não oficiais com o WhatsApp.
Como validar a assinatura HMAC do webhook no n8n?
Ligue Raw Body nas opções do node Webhook e adicione um Code node depois dele. No código, leia os bytes com this.helpers.getBinaryDataBuffer(0, 'data'), calcule crypto.createHmac('sha256', segredo).update(bytes) no formato que a documentação indicar (hex ou base64) e compare com o header x-heropay-signature usando crypto.timingSafeEqual. O módulo crypto já vem liberado no n8n Cloud; no self-hosted, defina NODE_FUNCTION_ALLOW_BUILTIN=crypto. Guarde o segredo em Variables ou variável de ambiente, nunca no código do node. Assinatura inválida responde 401.
Por que meu webhook do n8n não recebe o pagamento?
Quase sempre é uma destas três causas. Primeira: você registrou a Test URL (/webhook-test/), que só escuta com o editor aberto em modo de teste; registre a Production URL (/webhook/). Segunda: o workflow não está ativo, e a Production URL só responde com ele ativado. Terceira: o webhook foi registrado na API de produção enquanto você testa no sandbox, ou o contrário. Confira com GET /webhook na API certa e olhe a aba Executions, onde ficam as execuções de produção.
Como evitar dar baixa duas vezes no mesmo pagamento?
Trate todo evento como possivelmente repetido, porque a retentativa automática pode reenviar. No workflow pronto, o IF só marca como pago se o pedido ainda estiver pending e o valor em centavos bater. Em volume alto, use uma tabela no Postgres ou MySQL com índice único na chave gatilho + payment.id e faça o insert antes de agir: se falhar por duplicidade, pare o ramo. Planilha serve para começar, mas não é trava contra execuções simultâneas.
Funciona no n8n Cloud e no self-hosted?
Funciona nos dois, só com nodes nativos. No Cloud, o crypto do Code node já está liberado e a Production URL do Webhook é pública. No self-hosted, libere o módulo com NODE_FUNCTION_ALLOW_BUILTIN=crypto e garanta que o n8n está atrás de HTTPS com um domínio público (o HeroPay precisa alcançar a URL). Em localhost, use um túnel como cloudflared ou ngrok só para testar no sandbox.
Dá para cobrar mensalidade recorrente pelo n8n?
Dá de dois jeitos. O primeiro é criar um link de assinatura: mande period: "monthly" (ou quarterly, semiannual, annual) no POST /payment_links e o HeroPay cobra cada ciclo sozinho, avisando pelos gatilhos subscription_activate, subscription_update e subscription_cancel. A recorrência aceita cartão, boleto e Pix, e no Pix ela roda como Pix Automático. O segundo é um Schedule Trigger no n8n que gera um link avulso por mês para cada cliente da planilha, útil quando o valor muda todo mês.
Posso cobrar a partir de uma planilha do Google em vez do formulário?
Pode. Troque o Form Trigger do workflow pelo Google Sheets Trigger no evento de linha adicionada e mapeie as colunas para os campos do Code node "Preparar pedido". O resto do workflow não muda: o HTTP Request cria o link, a mesma planilha guarda o checkout_url e o Webhook dá baixa pela coluna pedido_id. Mantenha o valor em centavos ou deixe o Code node converter, e nunca deixe o cliente final editar a planilha de onde sai o preço.
Quanto custa usar o HeroPay com o n8n?
A integração não tem custo: o HeroPay não cobra por chamada de API nem por webhook. Você paga por transação aprovada: Pix R$ 0, boleto R$ 0 e cartão 3,49%, em até 12x. Sem mensalidade, ativação ou mínimo, e o sandbox é gratuito. Do lado do n8n, vale o seu plano do Cloud ou o custo do servidor no self-hosted. Numa carteira de 200 cobranças Pix de R$ 150 por mês, a tarifa do HeroPay é R$ 0. Veja preços.
Dá para o n8n criar cobrança a partir de uma conversa com IA?
Dá. No node AI Agent do n8n, conecte o HTTP Request configurado nesta página como ferramenta, com a descrição "cria um link de pagamento HeroPay; recebe produto e valor em centavos". Assim, uma conversa no WhatsApp ou no chat vira cobrança. Duas travas são obrigatórias: o preço vem de uma tabela sua, nunca do que o modelo ou o cliente disser, e a baixa continua vindo só do Webhook assinado. Para integrar por código com Claude, ChatGPT, Cursor, Lovable e outros, veja HeroPay para IA.
Como não expor a chave ao compartilhar o workflow?
Guarde o token numa credencial Header Auth e o segredo do webhook em Variables ou variável de ambiente. O JSON exportado leva só o nome da credencial, sem o valor. Antes de publicar um template, procure por Bearer, SECRET e pelo início do seu token no arquivo, e limpe o pinData (dados fixados de teste podem conter e-mail e telefone reais de compradores). Se o token vazou, regenere no painel do HeroPay e atualize só a credencial: nenhum node precisa mudar.
Comece agora
Importe o workflow, crie a conta sandbox e faça a primeira cobrança de teste em minutos. Sem fila de homologação: quando passar no sandbox, troque a chave e vá pro ar.