Para cobrar assinatura com retentativa no HeroPay, crie um link com period em POST /payment_links: o HeroPay cobra cada ciclo e reprocessa sozinho a cobrança recusada. Seu código só reage aos webhooks do ciclo de vida.
O period aceita monthly, quarterly, semiannual ou annual, e a assinatura aceita cartão, boleto e Pix. Não existe job de retentativa do seu lado: payment_credit_cart_refused avisa a recusa com o motivo da operadora, spark_payment_confirmed e subscription_update avisam que a cobrança entrou, e subscription_cancel avisa quando a assinatura cai. Cobrança recusada não paga tarifa.
Criar conta sandbox Ler a documentação
Antes de começar
- Conta sandbox e token JWT em app.heropay.tech, guardados em
HEROPAY_API_URLeHEROPAY_JWT_TOKENno servidor. - Um endpoint HTTPS para webhooks, que valide
X-HeroPay-Signature(código em /webhooks). - Uma tabela de assinantes no seu banco com, no mínimo: seu ID de cliente, o
srcque você usou e o status de acesso.
Passo a passo
1. Crie o plano como link recorrente
curl -X POST "$HEROPAY_API_URL/payment_links" \
-H "Authorization: Bearer $HEROPAY_JWT_TOKEN" \
-H "Accept: application/vnd.herospark.com; version=1" \
-H "Content-Type: application/json" \
-d '{
"payment_link": {
"name": "Plano Pro mensal",
"description": "Plano Pro, cobrado todo mês",
"price_cents": 9700,
"absorbs_fees": true,
"period": "monthly",
"frequency_type": "unlimited",
"payment_methods": ["credit_card", "bank_slip", "pix"],
"src": "cliente-1042"
}
}'Resposta 201 Created (recorte):
{
"message": "Payment link created successfully",
"data": {
"id": 137,
"price_cents": 9700,
"period": "monthly",
"frequency_type": "unlimited",
"frequency_limit": null,
"overdue_type": "none",
"overdue_limit": null,
"offer": {
"id": 6095,
"kind": "payment_link_subscription",
"url": "https://pay.beta.herospark.com/fc280e25-7cbd-446f-b34f-5b8824bb5124-6095?src=cliente-1042",
"accepted_payment_methods": ["credit_card", "bank_slip", "pix"]
}
}
}data.offer.url é o botão "Assinar". Para um plano com fim, troque para "frequency_type": "limited" e "frequency_limit": 12: a assinatura encerra sozinha depois de 12 cobranças.
Métodos: a assinatura aceita cartão, boleto e Pix (/assinaturas). No Pix, a recorrência é o Pix Automático, e a regra do Banco Central exige recebedor com CNPJ. Confira em data.offer.accepted_payment_methods quais métodos ficaram ativos no link.
2. Deixe a retentativa com o HeroPay
Quando a renovação falha, o HeroPay reprocessa a cobrança automaticamente. Você não agenda nada. Três regras para saber o que esperar:
- Tentativa recusada não paga tarifa. Cartão cobra 3,49% só na transação aprovada (preços).
- Recusa por limite é a mais recuperável: o limite disponível do cartão muda de um dia para outro, e é nela que a retentativa mais resolve.
- Recusa irreversível (cartão cancelado, bloqueado, vencido) não se resolve retentando: o caminho é o assinante trocar o cartão, e a troca é self-service, sem passar pelo seu suporte.
O calendário de tentativas segue a regra do HeroPay e os limites das bandeiras; você não agenda nem configura nada.
3. Cadastre os gatilhos do ciclo de vida
Um POST /webhook por gatilho, com a mesma URL:
for trigger in subscription_activate spark_payment_confirmed \
payment_credit_cart_refused subscription_update subscription_cancel; do
curl -s -X POST "$HEROPAY_API_URL/webhook" \
-H "Authorization: Bearer $HEROPAY_JWT_TOKEN" \
-H "Accept: application/vnd.herospark.com; version=1" \
-H "Content-Type: application/json" \
-d "{\"webhook\":{\"trigger\":\"$trigger\",\"webhook_url\":\"https://seuapp.com/webhooks/heropay\",\"request_method\":\"post\"}}"
done| Gatilho | Quando chega | O que o seu sistema faz |
|---|---|---|
subscription_activate | Primeiro pagamento confirmado | Cria a conta e libera o acesso |
spark_payment_confirmed | Cada cobrança paga, inclusive após retentativa | Estende o acesso até subscription.next_invoice_at |
payment_credit_cart_refused | Renovação recusada | Mostra aviso no app e orienta o assinante a trocar o cartão |
subscription_update | Mudança de status ou novo pagamento | Sincroniza o status local |
subscription_cancel | Cancelada por cliente, vendedor, estorno, chargeback ou atraso | Suspende o acesso |
O evento de recusa traz o motivo. Recorte dos campos que o handler usa (valores ilustrativos, campos reais do template; o payload completo está na documentação de webhooks):
{
"payment": { "id": "8812399", "method": "credit_card" },
"cart": { "src": "cliente-1042" },
"subscription": { "id": "3020", "next_invoice_at": "2026-10-23" },
"payment_methods": {
"credit_card": { "refused_message": "Saldo insuficiente" }
}
}O texto de refused_message vem da operadora e varia: mostre ou registre, mas não faça if sobre a frase exata. Decida pelo gatilho: não suspenda o acesso na primeira recusa, porque a retentativa ainda vai rodar, e suspenda em subscription_cancel. Deduplique por payment.id + gatilho.
4. Monitore e cancele por API
Lista de assinaturas com cobrança recusada, para um painel de risco:
curl -G "$HEROPAY_API_URL/sales/recurring" \
-H "Authorization: Bearer $HEROPAY_JWT_TOKEN" \
-H "Accept: application/vnd.herospark.com; version=1" \
--data-urlencode "paymentStatus[]=refused" \
-d "limit=100"Cada item traz cart.subscriptionId, cart.subscriptionStatus e a lista purchases com cada tentativa. Para cancelar quando o cliente pede:
curl -X POST "$HEROPAY_API_URL/recurring_payment/cancel" \
-H "Authorization: Bearer $HEROPAY_JWT_TOKEN" \
-H "Accept: application/vnd.herospark.com; version=1" \
-H "Content-Type: application/json" \
-d '{ "recurring_payment": { "recurring_payment_id": 3020 } }'Resposta 200: "message": "Recurring payment cancelled successfully" e data.status: "cancelled". O cancelamento é imediato e irreversível; o histórico fica preservado.
Variações
Por prompt, com o llms.txt
Cole https://heropay.ai/llms.txt no Claude, ChatGPT, Cursor, Lovable e outros (mais em /ai) e peça:
Cria um plano mensal de R$ 97 no cartão, renovação ilimitada, src "cliente-1042",
e cadastra webhooks de recusa, pagamento confirmado e cancelamento de assinatura
para https://seuapp.com/webhooks/heropay.Com o contrato da API no contexto, a IA monta o POST /payment_links com period e os cadastros de webhook. Revise as chamadas antes de rodar. Um MCP server oficial está em desenvolvimento.
Sem código, pelo painel
Crie o link recorrente em app.heropay.tech escolhendo a periodicidade e a duração, copie a URL e use no botão "Assinar".
Erros comuns e como ler a mensagem
| Status | Mensagem (real) | Causa e correção |
|---|---|---|
422 | Price cents must be greater than or equal to 500 | Valor do ciclo abaixo de R$ 5,00 ou enviado em reais. |
422 | Period is not included in the list | period fora de monthly, quarterly, semiannual, annual (não existe weekly nem yearly). |
| sem erro de validação | nenhuma | "limited" enviado sem frequency_limit: a validação da API não bloqueia a combinação. Mande sempre os dois juntos e confira frequency_limit na resposta. |
422 (cancelamento) | Recurring payment id can't be blank | Faltou recurring_payment.recurring_payment_id. |
404 ou 400 (cancelamento) | Failed to cancel recurring payment | ID inexistente, de outra conta ou assinatura já cancelada. Consulte GET /sales/recurring antes de repetir. |
422 (listagem) | Limit deve ser um número inteiro entre 10 e 1000 | limit fora da faixa em /sales/recurring. |
Perguntas frequentes
Quantas vezes o HeroPay retenta uma assinatura recusada?
O reprocessamento é automático, segue a regra do HeroPay e os limites das bandeiras, e você não configura job. O que vale para o seu planejamento: tentativa recusada não paga tarifa, a recusa por limite é a mais recuperável e o assinante pode trocar o cartão sozinho quando retentar não resolve. As bandeiras também limitam retentativas no mesmo cartão, por isso retentativa sozinha nunca é a estratégia inteira.
Devo cortar o acesso quando chega payment_credit_cart_refused?
Não. A recusa é o começo da recuperação, não o fim da assinatura. Mostre um aviso no app, oriente a troca de cartão e mantenha o acesso enquanto a retentativa roda. Corte quando chegar subscription_cancel, que é disparado quando a assinatura cai por atraso, cancelamento, estorno ou chargeback. Cortar cedo transforma churn involuntário em churn real.
Como leio o motivo da recusa?
No campo payment_methods.credit_card.refused_message do webhook payment_credit_cart_refused. O texto vem da operadora. O texto vem da operadora e varia, então use para orientar a mensagem, sem depender da frase exata: limite pede outro método, cartão vencido pede troca de cartão. Para ver o padrão da base inteira, GET /reports/purchase/refused/reasons_count agrupa as recusas por motivo num período (/como/recuperar-vendas-recusadas).
Dá para cobrar assinatura por Pix ou boleto?
Sim. A assinatura aceita cartão, boleto e Pix. No Pix, a recorrência é o Pix Automático: o cliente autoriza uma vez e as cobranças seguintes caem sozinhas, e a regra do Banco Central exige recebedor com CNPJ. Pix e boleto custam R$ 0 por cobrança. Mande os métodos em payment_methods e confira accepted_payment_methods na resposta.
Como cancelo uma assinatura pela API?
Com POST /recurring_payment/cancel e o corpo {"recurring_payment": {"recurring_payment_id": 3020}}. A resposta traz status: cancelled, o cancelamento é imediato, não gera cobranças futuras e não pode ser desfeito. Apenas assinaturas ativas podem ser canceladas; repetir o pedido numa assinatura já cancelada devolve Failed to cancel recurring payment. O webhook subscription_cancel chega em seguida para você suspender o acesso.
Quanto custa a recorrência?
Não há preço à parte para assinatura nem para retentativa. Cada cobrança aprovada paga a tarifa normal: cartão 3,49%, Pix R$ 0 e boleto R$ 0. Numa assinatura de R$ 97 no cartão, cada ciclo aprovado custa R$ 3,39; as tentativas recusadas custam zero. Sem mensalidade, sem ativação, sem mínimo. Tabela em /precos.