← Todos los artículos

Cómo funciona realmente el seguimiento de afiliados con Stripe: cookies, webhooks y reversiones

Publicado · El equipo de Ambassly

Si vendes mediante Stripe y estás configurando un programa de afiliación —o construyendo el seguimiento por tu cuenta—, debes resolver cuatro problemas: llevar un código de referido hasta el visitante, trasladarlo al objeto de Stripe, recibir de forma fiable la información del pago y gestionar los reembolsos sin pagar comisiones sobre dinero devuelto. Así funciona cada parte.

1. Llevar el código hasta el visitante

Un enlace de afiliado se parece a yoursite.com/?via=CODE o apunta a una redirección corta como yoursite.com/r/CODE. La redirección resulta más limpia porque nunca muestra el código en la barra de direcciones y permite registrar el clic en el servidor antes de enviar al visitante a su destino.

Cuando llega, necesitas recordar el código hasta que convierta, ya sea unos minutos o varias semanas después. El enfoque habitual es una cookie propia, por ejemplo ambassly_ref, configurada durante 60 días con SameSite=Lax, además de una copia en localStorage como vía de lectura de respaldo.

Esta es la parte que omiten muchos tutoriales: si creas la cookie desde JavaScript en el cliente, Intelligent Tracking Prevention de Safari limita su duración a unos siete días, con independencia de la caducidad solicitada. Un visitante que guarda un tutorial y vuelve a comprar tres semanas después con Safari pierde la atribución silenciosamente. La solución es establecer la cookie mediante una cabecera HTTP Set-Cookie enviada por el servidor, en lugar de document.cookie, ya que el límite de ITP se aplica específicamente a las cookies del cliente, no a las del servidor. Si no puedes hacerlo en todas las solicitudes, como mínimo explica la limitación a tus afiliados en lugar de informar de menos conversiones sin avisar.

2. Llevar el código al objeto de Stripe

La cookie solo ayuda hasta el momento de la compra. Después, el código debe viajar con el pago, porque las cookies no siempre sobreviven a la redirección hacia Checkout alojado por Stripe y al regreso en todas las configuraciones del navegador.

Stripe ofrece dos lugares:

  • client_reference_id en una Checkout Session, un campo de texto diseñado precisamente para adjuntar tu propia referencia a un pago.
  • metadata, un objeto libre de clave y valor disponible en Checkout Sessions, PaymentIntents y Subscriptions.

Usar ambos es el patrón más seguro. Asigna el código de afiliado a client_reference_id y el mismo valor a metadata.ambassly_ref (o la clave que elijas). client_reference_id es el campo único más idiomático, pero los metadatos llegan a objetos posteriores, como las facturas, de formas que a veces facilitan las consultas. Esta redundancia no supone ningún coste.

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

3. Recibir la información de forma fiable: webhooks e idempotencia

Ahora necesitas saber cuándo termina realmente la Checkout Session y, si se trata de una suscripción, cuándo se pagan las facturas posteriores. Para ello necesitas un endpoint de webhook que escuche como mínimo:

  • checkout.session.completed, para resolver el código y crear el registro inicial del referido.
  • invoice.paid, para crear una comisión en cada ciclo de facturación de los programas recurrentes.
  • charge.refunded y customer.subscription.deleted, para activar reversiones.

El detalle importante es la idempotencia. La documentación de webhooks de Stripe explica que el endpoint recibirá ocasionalmente el mismo evento más de una vez, porque Stripe reintenta cuando no obtiene una respuesta 2xx correcta y porque hay fallos de red en ambos extremos. Si el controlador no es idempotente, el reintento de un evento invoice.paid crea una segunda comisión para la misma factura, de modo que deberías pagar dos veces al afiliado por un único cobro.

La solución es sencilla: mantén una tabla de ID de eventos de Stripe procesados (stripe_event_id con restricción única) e inserta el ID antes de hacer cualquier otra cosa en el controlador. Si la inserción falla por la restricción única, ya habías visto el evento: omítelo y devuelve 200. Todos los efectos posteriores —crear una comisión o actualizar el estado de un referido— solo deben ejecutarse después de que la inserción tenga éxito.

4. Reversiones por reembolso

La última parte es la que todos los programas acaban necesitando y pocos construyen desde el principio: qué ocurre cuando el cliente recibe un reembolso o cancela y el pago se revierte. Si ya marcaste una comisión como pending o approved y se reembolsa el cargo correspondiente, la comisión debe pasar a un estado de reversión, no quedarse silenciosamente en los libros.

Si ya pagaste la comisión antes del reembolso, no puedes recuperar una transferencia enviada. El enfoque honesto es un ajuste negativo: un nuevo asiento en el libro mayor que se descuenta del siguiente pago al afiliado. En ambos casos, esto solo funciona si cada comisión conserva un historial de eventos y no únicamente su estado actual, para que tú y el afiliado podáis ver por qué cambió.

Por eso también importa el periodo de retención previo al pago. Si esperas entre 30 y 45 días antes de que una comisión sea pagadera, aproximadamente lo mismo que el plazo de reembolso, la mayoría de los reembolsos llegan antes de pagar a nadie y casi desaparecen las reversiones posteriores al pago.

Construirlo o comprarlo

Nada de esto es exótico. Es una cookie, dos campos de Stripe, un controlador de webhook idempotente y una máquina de estados con historial de auditoría. Muchos equipos lo construyen durante un fin de semana y funciona bien durante un tiempo. Suele fallar precisamente en los casos anteriores: el límite de cookies de Safari, el webhook que se reintenta y el reembolso que llega después de haber enviado el pago. Creamos Ambassly para resolver correctamente esos tres modos de fallo una sola vez, en lugar de que cada equipo de herramientas de desarrollo los redescubra por su cuenta.

Si vas a desarrollarlo internamente, merece la pena leer de principio a fin la documentación de Stripe sobre Connect y el tratamiento idempotente de solicitudes antes de publicar tu primer controlador de webhooks. Es una tarde bien invertida en cualquier caso.