Voltar ao blog

Como adicionar pagamentos ao seu SaaS com Stripe

Guia completo pra adicionar assinaturas ao seu SaaS com Stripe: Checkout, os eventos de webhook que importam, verificação de assinatura e teste

C
Caio Braga
14 de julho de 2026 · 8 min de leitura
Sumário do artigo
Como adicionar pagamentos ao seu SaaS com Stripe

Todo micro SaaS chega no momento de cobrar, e é aí que a maioria trava. A boa notícia: com o Stripe, você não constrói um sistema financeiro, você conecta três peças: o Checkout (a página de pagamento), os webhooks (os avisos que o Stripe manda) e uma tabela de status no seu banco. A má notícia: existe um jeito certo e vários errados de ligar essas peças, e os errados só aparecem quando um cliente reclama de cobrança.

Este guia mostra o caminho com os nomes reais das coisas: quais eventos escutar, como verificar a assinatura do webhook, quais cartões de teste usar. A referência de backend é o Supabase, mas a lógica vale pra qualquer stack.

O que você vai precisar

Uma conta no Stripe (stripe.com), que já nasce com o modo de teste habilitado. Você só precisa ativar a conta com dados reais quando for cobrar de verdade.

Seu SaaS rodando, com uma página de preços onde vai entrar o botão de assinar.

Se o backend é Supabase, uma edge function vai receber os webhooks. Em outra stack, qualquer endpoint HTTP público resolve.

Duas chaves que você pega no painel do Stripe (em Developers, na seção API keys): a chave publicável (começa com pk_test_) e a secreta (começa com sk_test_). A secreta nunca vai pro frontend, ela vive só no backend ou nas variáveis de ambiente.

Como o fluxo funciona

O cliente clica em assinar. Seu backend cria uma Checkout Session no Stripe e devolve uma URL. O cliente é redirecionado pra essa URL, que é uma página do próprio Stripe, e preenche o cartão lá (o número do cartão nunca passa pela sua aplicação, o que te livra de quase toda a dor de conformidade PCI). O pagamento é aprovado, o Stripe redireciona o cliente de volta pra sua página de sucesso, e, em paralelo, manda um evento de webhook pro seu backend confirmando. Seu backend recebe o evento, valida que veio mesmo do Stripe, e libera o acesso.

A regra de ouro que resolve 90% dos bugs de pagamento: quem libera acesso é o webhook, nunca a página de sucesso. O cliente pode fechar a aba antes de voltar, a rede pode falhar no redirect, e a página de sucesso pode até ser acessada direto por alguém esperto com a URL. O webhook é a única fonte de verdade.

Passo 1: crie os produtos e preços no Stripe

No painel, na seção Product catalog, crie um produto pra cada plano do seu SaaS (por exemplo "Plano Pro") e, dentro dele, um preço recorrente (mensal, anual, ou os dois). Cada preço ganha um ID que começa com price_. Guarde esses IDs: são eles que o seu código usa, não os nomes.

Um detalhe de quem já se arrependeu: se um dia você mudar o valor do plano, não edite o preço existente, crie um preço novo no mesmo produto e arquive o antigo. Assinaturas antigas continuam no preço antigo, novas entram no novo, e você acabou de implementar "grandfathering" sem escrever uma linha.

Passo 2: crie a Checkout Session no backend

O botão de assinar não chama o Stripe direto do navegador, ele chama o seu backend, que cria a sessão usando a chave secreta. No Supabase, isso é uma edge function que faz um POST pra API do Stripe criando uma sessão com estes campos essenciais:

mode: "subscription"
line_items: [{ price: "price_XXXX", quantity: 1 }]
success_url: "https://seuapp.com/sucesso?session_id={CHECKOUT_SESSION_ID}"
cancel_url: "https://seuapp.com/precos"
client_reference_id: o ID do usuário logado no SEU sistema

O campo mais importante dessa lista é o client_reference_id. É ele que faz a ponte entre "alguém pagou no Stripe" e "o usuário fulano do meu banco". Sem ele, o webhook chega e você não sabe quem liberar. Passe ali o ID do usuário autenticado (no Supabase, o user.id da sessão).

A função devolve a url da sessão, e o frontend só faz window.location.href = url.

Passo 3: escute os webhooks certos

No painel do Stripe, em Developers e depois Webhooks, cadastre o endpoint do seu backend (a URL pública da sua edge function). O Stripe manda dezenas de tipos de evento, mas pra um SaaS de assinatura você precisa de quatro:

checkout.session.completed: o cliente pagou pela primeira vez. É aqui que você libera o acesso. O evento traz o client_reference_id que você passou no passo 2, e os IDs de customer e subscription que você vai guardar.

customer.subscription.updated: a assinatura mudou (upgrade, downgrade, ou o status virou past_due porque uma cobrança falhou). Atualize o plano e o status no seu banco.

customer.subscription.deleted: a assinatura acabou de vez (cancelada e chegou ao fim do período). É aqui que você revoga o acesso, e não antes.

invoice.payment_failed: uma cobrança recorrente falhou. Não corte o acesso na hora: o Stripe entra sozinho no modo de retentativa (o "dunning" configurável no painel, em Billing) e tenta de novo por alguns dias. Use esse evento pra avisar o cliente por e-mail que o cartão precisa de atenção.

Ao criar o endpoint no painel, o Stripe te dá um "signing secret" que começa com whsec_. Guarde nas variáveis de ambiente, você vai usar já no próximo passo.

Passo 4: verifique a assinatura do webhook (não pule)

Qualquer pessoa que descubra a URL do seu endpoint pode mandar um POST fingindo ser o Stripe, com um JSON dizendo "fulano pagou". A proteção contra isso é a assinatura: todo evento chega com um header stripe-signature, e a biblioteca do Stripe tem uma função que valida esse header contra o seu whsec_ (no SDK, é a stripe.webhooks.constructEvent, que recebe o corpo cru da requisição, o header e o secret, e explode com erro se a assinatura não bater).

Dois cuidados práticos que causam a maioria das falhas de verificação: a função precisa do corpo cru da requisição (se o seu framework fizer parse do JSON antes, a assinatura não bate), e responda o webhook com status 200 rápido, fazendo o processamento pesado depois, porque o Stripe reenvia eventos que não recebem 200 em alguns segundos, e aí você processa o mesmo pagamento duas vezes.

Sobre processar duas vezes: torne o handler idempotente. O jeito simples é guardar o event.id de cada evento processado numa tabela e ignorar IDs repetidos.

Passo 5: guarde o status no seu banco

Crie uma tabela subscriptions (ou equivalente) com pelo menos: o ID do usuário no seu sistema, o customer_id do Stripe (começa com cus_), o subscription_id (começa com sub_), o plano, o status (active, past_due, canceled) e a data de fim do período atual.

É essa tabela, e não a API do Stripe, que a sua aplicação consulta a cada acesso pra decidir o que o usuário pode fazer. Consultar o Stripe em tempo real a cada clique seria lento e esbarraria em limites de requisição.

Passo 6: deixe o cliente gerenciar a própria assinatura

Você não precisa construir telas de "trocar cartão", "ver faturas" e "cancelar assinatura": o Stripe tem o Customer Portal pronto. Habilite no painel (em Settings, Billing, Customer portal), e no seu backend crie uma sessão de portal passando o customer_id do cliente. Ele devolve uma URL, você redireciona, e o cliente resolve tudo lá, com as mudanças chegando pra você pelos mesmos webhooks do passo 3.

Um botão "Gerenciar assinatura" que abre o portal economiza semanas de desenvolvimento e um bocado de tickets de suporte.

Passo 7: teste o ciclo inteiro no modo de teste

No modo de teste, use os cartões fictícios do Stripe: 4242 4242 4242 4242 (aprova sempre, com qualquer validade futura e qualquer CVC) e 4000 0000 0000 0002 (recusa sempre), entre outros que a documentação lista pra cenários como 3D Secure.

Teste a sequência completa: assinar com o cartão que aprova, conferir que o webhook chegou e a tabela atualizou, abrir o Customer Portal, cancelar, e conferir que o acesso caiu só no fim do período. Pra desenvolver localmente sem expor endpoint público, a Stripe CLI tem o comando stripe listen, que encaminha os webhooks pro seu localhost e te dá um whsec_ temporário.

Só troque as chaves sk_test_ e pk_test_ pelas de produção quando esse ciclo estiver redondo de ponta a ponta.

Cuidados que evitam dor de cabeça

Libere acesso só pelo webhook, nunca pela página de sucesso.

Revogue acesso no customer.subscription.deleted, não no clique de cancelar, porque o cliente pagou pelo período inteiro.

No invoice.payment_failed, avise o cliente e deixe o dunning do Stripe trabalhar, em vez de cortar o acesso na primeira falha.

Guarde o event.id processado pra não executar o mesmo evento duas vezes.

E um aviso de casa: preço em real funciona normalmente no Stripe (moeda BRL), mas confira as taxas pra sua conta e, se o seu público paga por Pix, saiba que o Stripe tem suporte a Pix no Brasil, habilitado no painel em Payment methods.

Conclusão

Pagamento em SaaS não é construir sistema financeiro, é ligar bem quatro pontos: Checkout Session com client_reference_id, os quatro eventos de webhook que importam, verificação de assinatura com idempotência, e uma tabela de status que a sua aplicação consulta. O Customer Portal completa o pacote sem você escrever telas de cobrança.

Se você constrói no Lovable , a integração com Stripe entra por uma edge function do Supabase exatamente como descrito aqui, e o padrão de webhook com verificação de assinatura é o mesmo. Teste tudo no modo de teste primeiro, e só vire a chave de produção quando o ciclo completo, do checkout ao cancelamento, estiver comprovado.

Tags
#SaaS#Stripe#pagamentos#assinatura#micro SaaS#Supabase
● Não perca essa chance

Não perca a próxima edição.

Toda quinta, 9h. Direto na sua caixa.

  • Ferramentas que economizam horas do seu trabalho
  • Agentes e automações que funcionam
  • Bastidores do que estamos construindo

100% gratuito. Cancele quando quiser.

Compartilhar