Un webhook respondió con HTTP 302: por qué llegan reintentos y qué debe hacer mientras tanto el estado de verificación
Una pregunta pública describe un receptor de webhooks de PayPal que responde con HTTP 302 y luego recibe reintentos. No vemos esa aplicación, no representamos a su autor y no afirmamos arreglar PayPal ni ninguna plataforma. Sin embargo, el problema adyacente en el onboarding de identidad es concreto. Una redirección no es un acuse de recibo. Cuando una llamada no se confirma, el emisor puede reintentarla, o puede que nunca llegue a procesarse. En cualquiera de los dos casos, su servicio de onboarding debe mantener un estado honesto. La decisión del lector es esta: cuando una llamada de verificación rebota, se duplica o nunca llega, ¿qué debe registrar su aplicación y qué debe negarse a hacer? Este artículo responde a esa pregunta usando solo el ejemplo sintético local verificado. En todo momento separa el diseño de aplicación propuesto de lo que el fixture implementa realmente.
curl https://gridzen.ai/console/api/quickstart/trial \
-H 'Content-Type: application/json' \
-d '{"task": "identity_onboarding", "scenario": "success"}'El problema: una redirección parece éxito, pero no lo es
Muchos frameworks redirigen por defecto las peticiones no autenticadas o no canónicas. Las causas habituales son un middleware de inicio de sesión, una reescritura de HTTP a HTTPS o una regla sobre la barra final. En sus registros, la petición parece atendida. Para un emisor de webhooks, en cambio, un 302 no suele ser el acuse de recibo que espera, así que programa otra entrega. De ahí surgen dos riesgos. Primero, el mismo evento puede llegar varias veces cuando se corrija la ruta. Segundo, mientras persista la redirección, el evento puede no llegar nunca a su manejador, y una sesión queda en pending indefinidamente. Diseño propuesto, no comportamiento del fixture: excluya la ruta de callback del middleware de inicio de sesión y de redirección. Responda con un estado de éxito directo solo después de guardar el evento de forma duradera. Trate cualquier otro resultado como no entregado. El fixture no tiene receptor HTTP ni lógica de redirección, así que no puede mostrar esta parte. Muestra qué ocurre con el estado cuando los eventos llegan o no llegan.
Lo que implementa el fixture
La clase Identity persiste sesiones y eventos en SQLite. El paso create guarda una sesión pending con un plazo y devuelve un registro simulado. Ese registro contiene hosted_link en una dirección example.invalid, link_executable false y next send_real_provider_link_only_after_authorization. El paso event solo acepta mode synthetic_fixture y source fixture_callback. Cualquier otra entrada lanza PRODUCTION_SIGNATURE_VERIFIER_NOT_IMPLEMENTED, así que no hay verificación de firmas reales. Los identificadores de evento deben empezar por fixture-. El paso event también exige el tenant y el subject guardados. Una discrepancia, o una sesión desconocida, lanza BINDING_MISMATCH, mientras que leer una sesión desconocida lanza SESSION_NOT_FOUND. Cada evento se guarda bajo una clave primaria formada por sesión y evento, de modo que un evento repetido devuelve el estado actual sin cambios. El paso recover marca como expired las sesiones pending, review o unknown cuyo plazo ya ha vencido. Lo hace localmente y devuelve provider_polled false.
Ejemplo hipotético: una aprobación reintentada y luego silencio
Escenario hipotético. Se corrige un fallo de redirección y llegan los reintentos que el emisor tenía en cola. En el fixture se crea una sesión en el tiempo 0 con un timeout de 60, así que deadline es 60. El evento fixture-approved llega con status Approved y policy true. La salida probada es status approved, permission_granted true, grant_count 1, live_permission_changed false, policy_version 1 y allow_action true. Después, el mismo evento vuelve a llegar como reintento. La salida es idéntica y grant_count sigue en 1. Una corrección posterior Declined da status declined, permission_granted false y grant_count 1. Ahora tome el otro camino, en el que nunca llega ninguna llamada. Llamar a recover en el tiempo 61 sobre una sesión pending nueva devuelve status expired, permission_granted false, grant_count 0, next review_or_authorized_provider_refresh y provider_polled false. El silencio nunca se convirtió en aprobación.
Manejo de fallos que corresponde a su aplicación
Diseño propuesto. Registre toda respuesta no 2xx o de redirección en la ruta de callback como un defecto de entrega, no como un resultado de verificación. Persista el evento antes de confirmarlo, para que los reintentos sean inofensivos. Ejecute la recuperación de forma programada contra el plazo guardado. Tras el vencimiento, envíe el caso a revisión o a una actualización de estado autorizada con su proveedor. El fixture no realiza esa actualización. Trate BINDING_MISMATCH y SESSION_NOT_FOUND como rechazos que hay que investigar, nunca como motivos para crear una sesión nueva. El permission_granted del fixture es un indicador local de demostración. Cualquier permiso real requiere su propia integración, evidencia actual y una política explícita.
Límites de este ejemplo
El fixture solo acepta callbacks sintéticos. No valida firmas reales de proveedores, no consulta a ningún proveedor y no reproduce redirecciones HTTP. Sus registros de concesión son datos del fixture, así que un permiso sintético no es un permiso de negocio real. Las pruebas cubren transiciones simuladas, vinculaciones, callbacks duplicados, reinicio y vencimientos. No demuestran éxito en producción. Las pruebas de identidad alojada son una vía aparte y limitada para empresas elegibles de GB/US. La acción de extremo a extremo sobre un usuario real recién aprobado sigue sin verificarse.
Lista de aceptación
Antes de avanzar hacia una integración en sandbox, confirme cada uno de estos puntos. 1. La ruta de callback no devuelve redirecciones ni requiere inicio de sesión. 2. Los eventos se guardan antes de confirmarse. 3. Un evento duplicado deja grant_count sin cambios. 4. Un estado Declined posterior a la aprobación borra permission_granted. 5. Una llamada a recover tras el plazo da expired con provider_polled false, y no da aprobación. 6. Un tenant o subject que no coincide lanza BINDING_MISMATCH. 7. El estado sobrevive a un reinicio con la misma base SQLite. 8. La verificación de firmas reales y la actualización de estado autorizada se planifican por separado. Ejecute primero las pruebas de reinicio y de vencimiento de la guía.
Ejecutar el ejemplo sintético comprobado
Descargue y extraiga https://gridzen.ai/downloads/verification-delivery.zip, entre en verification_delivery y ejecute con Python 3.10+. El ejemplo local no hace llamadas de red. Use una base nueva para otra prueba de identidad.
python3 delivery.py identity --db /tmp/gridzen-article-fixture.sqliteFuentes y alcance
https://gridzen.ai/guides/missing-verification-webhook/ https://gridzen.ai/guides/current-capabilities/ Basado en las fuentes comprobadas y el ejemplo local; las reglas propuestas son recomendaciones de diseño, no comportamiento de producción demostrado. Redacción asistida por IA con revisión independiente.
Probar por separado tres resultados del sandbox Crear una cuenta para continuar la integración
Una descarga no demuestra una ejecución correcta. Los ejemplos locales y el sandbox en línea se prueban por separado.