← Todos os artigos

Como funciona realmente o tracking de afiliados com Stripe: cookies, webhooks e reversões

Publicado · A equipa da Ambassly

Se vende através da Stripe e está a configurar um programa de afiliados — ou a construir o tracking internamente —, existem quatro problemas: levar um código de indicação ao visitante, transferi-lo para o objeto da Stripe, receber os pagamentos de forma fiável e tratar reembolsos sem pagar comissões sobre dinheiro devolvido. Eis como funciona cada parte.

1. Levar o código ao visitante

Um link de afiliado assemelha-se a yoursite.com/?via=CODE ou aponta para um redirecionamento curto como yoursite.com/r/CODE. O redirecionamento é mais simples porque não mostra o código na barra de endereço e permite registar o clique no servidor antes de encaminhar o visitante.

Quando chega, é preciso recordar o código até à conversão, que pode acontecer minutos ou semanas depois. A abordagem habitual é um cookie próprio, por exemplo ambassly_ref, definido por 60 dias com SameSite=Lax, acompanhado por uma cópia em localStorage como leitura de reserva.

Esta é a parte omitida por muitos tutoriais: se definir o cookie com JavaScript no cliente, a Intelligent Tracking Prevention do Safari limita a duração a cerca de sete dias, independentemente da validade pedida. Um visitante que guarda um tutorial e regressa para comprar três semanas depois no Safari perde silenciosamente a atribuição. A solução é definir o cookie através do cabeçalho HTTP Set-Cookie enviado pelo servidor, em vez de document.cookie, pois o limite da ITP aplica-se aos cookies do cliente e não aos do servidor. Se não puder fazê-lo em todos os pedidos, pelo menos explique a limitação aos afiliados em vez de subestimar as conversões sem aviso.

2. Levar o código para o objeto da Stripe

O cookie só ajuda até ao momento da compra. Depois, o código tem de acompanhar o pagamento, porque os cookies nem sempre sobrevivem ao redirecionamento para o Checkout alojado pela Stripe e ao regresso.

A Stripe disponibiliza dois locais:

  • client_reference_id numa Checkout Session, um campo de texto criado precisamente para associar a sua referência a um pagamento.
  • metadata, um objeto livre de chave e valor disponível em Checkout Sessions, PaymentIntents e Subscriptions.

Usar ambos é o padrão mais seguro. Defina client_reference_id como o código de afiliado e atribua o mesmo valor a metadata.ambassly_ref (ou à chave escolhida). client_reference_id é o campo único mais idiomático, mas os metadados chegam a objetos posteriores, como faturas, de formas por vezes mais fáceis de consultar. Esta redundância não tem custo.

const session = await stripe.checkout.sessions.create({
  client_reference_id: referralCode,
  metadata: { ambassly_ref: referralCode },
  line_items: [...],
  mode: "subscription",
  success_url: "...",
  cancel_url: "...",
});

3. Receber eventos de forma fiável: webhooks e idempotência

É agora necessário saber quando a Checkout Session termina e, numa subscrição, quando as faturas seguintes são pagas. Isso exige um endpoint de webhook que escute pelo menos:

  • checkout.session.completed, para resolver o código e criar a indicação inicial.
  • invoice.paid, para criar uma comissão em cada ciclo de faturação dos programas recorrentes.
  • charge.refunded e customer.subscription.deleted, para acionar reversões.

O detalhe decisivo é a idempotência. A documentação de webhooks da Stripe explica que o mesmo evento pode chegar mais do que uma vez, porque a Stripe repete quando não recebe uma resposta 2xx correta e porque há falhas de rede em ambos os lados. Se o handler não for idempotente, uma repetição de invoice.paid cria uma segunda comissão para a mesma fatura e o afiliado recebe duas vezes por um único pagamento.

A solução é simples: mantenha uma tabela dos ID de eventos processados (stripe_event_id com restrição única) e insira o ID antes de qualquer outro efeito. Se a inserção falhar devido à restrição, o evento já foi visto: ignore-o e devolva 200. Todos os efeitos posteriores — criação de comissão ou atualização da indicação — só devem acontecer depois de a inserção ter sucesso.

4. Reversões por reembolso

A última parte é aquela de que todos os programas acabam por precisar e poucos criam de início: o que acontece quando o cliente recebe um reembolso ou cancela e o pagamento é revertido. Se uma comissão já estiver pending ou approved e o respetivo débito for reembolsado, deve passar para um estado de reversão, não ficar silenciosamente nos registos.

Se a comissão já foi paga, não é possível anular a transferência. A abordagem transparente é um ajuste negativo: uma nova entrada no livro-razão descontada do pagamento seguinte. Em ambos os casos, isto só funciona se cada comissão mantiver um histórico de eventos, não apenas o estado atual, para que a empresa e o afiliado vejam porque mudou.

É também por isso que importa um período de retenção. Esperar entre 30 e 45 dias antes de uma comissão se tornar pagável, aproximadamente a duração da janela de reembolso, faz com que a maioria dos reembolsos chegue antes do pagamento e praticamente elimina reversões posteriores.

Construir ou comprar

Nada disto é exótico: um cookie, dois campos da Stripe, um handler de webhook idempotente e uma máquina de estados com histórico de auditoria. Muitas equipas constroem o sistema num fim de semana e ele funciona durante algum tempo. Normalmente falha precisamente nos casos acima: limite dos cookies no Safari, webhook repetido e reembolso após o pagamento. Criámos a Ambassly para que estes três cenários sejam resolvidos corretamente uma só vez, em vez de cada equipa de ferramentas de desenvolvimento os redescobrir.

Se vai desenvolver internamente, vale a pena ler integralmente a documentação da Stripe sobre Connect e sobre o tratamento idempotente de pedidos antes de publicar o primeiro handler de webhook. É uma tarde bem empregue em qualquer caso.