Saltar al contenido principal

Registrar un envío de un formulario

POST/v1/forms/{id}/submissions

Para desarrolladores

Descripción​

Registra un envío de un formulario que su sistema presentó al contacto. El formulario se construye y configura en el panel; este endpoint permite presentarlo con su propio diseño y en su propio sitio, y el envío conserva todo el comportamiento configurado en el formulario:

  • Validación de cada valor según la configuración de su campo.
  • Doble confirmación por correo, cuando el formulario la exige.
  • Respuesta automática al contacto.
  • Aviso al equipo.
  • Registro de los consentimientos, con el texto del término aceptado.

El registro queda con source=form_api, que lo distingue tanto de un envío hecho en una página de Fidelizador como de un consentimiento declarado con POST /v1/consent-events, que no tiene formulario detrás.

Cuándo usar este endpoint​

SituaciónEndpoint
Necesita presentar el formulario con su propio diseño y en su propio sitio, conservando la configuración del formulario de FidelizadorEste endpoint, POST /v1/forms/{id}/submissions
Captó el consentimiento en un canal propio (un contrato, una llamada, su checkout) y solo necesita registrarloPOST /v1/consent-events
No necesita presentarlo en su propio sitioEl enlace público o el código de inserción que ofrece el panel
Importante

Al usar este endpoint, deberá agregar su propia protección anti-bot. El enlace público y el código de inserción del panel ya incluyen una verificación anti-bot. Los envíos hechos por este endpoint, no. Si el formulario que construye queda expuesto en internet, protéjalo antes de publicarlo. El detalle está en La protección anti-bot queda de su lado.

Guía relacionada​

  • Formularios — Arme formularios de captura y revise lo que se envió.

Autenticación​

Authorizationbearer tokenheaderobligatorio
API key con scope form:write. Formato: Bearer <API_KEY>.
X-Instance-Slugstringheaderobligatorio
Identificador de su instancia. Ver Autenticación.

Se recomienda leer el formulario con GET /v1/forms/{id} antes de cada envío, por lo que la API key necesita también el scope form:read. Un scope no incluye al otro: una clave que solo tiene form:write recibe un 403 al leer el formulario.

La protección anti-bot queda de su lado​

Al publicar el formulario con este endpoint, la protección anti-bot depende de su sitio.

Este endpoint no comprueba si quien envía el formulario es una persona. Una verificación anti-bot confirma que hay una persona frente a un navegador; una API key identifica a quien hace la llamada, que es su sistema. Son comprobaciones distintas, y un servidor no puede resolver un desafío anti-bot porque no tiene un navegador donde mostrarlo. Es lo habitual en las APIs que reciben formularios: autentican a quien integra, no a quien completó el formulario.

Por qué no sirve la verificación de Fidelizador. Un token de esa verificación se emite para el dominio de Fidelizador y solo allí se valida. Uno generado en su dominio sería rechazado.

Qué hacer. Si la página donde incluye el formulario está abierta en internet, agréguele su propia protección antes de publicarla. Sirve cualquiera de las habituales; lo recomendable es usar la que ya tenga en el resto de su sitio:

  • Una verificación anti-bot integrada en el navegador, como reCAPTCHA, Turnstile o hCaptcha.
  • Un campo trampa: un campo oculto que una persona nunca completa y que un robot sí completa.
  • Un límite de envíos por dirección IP.
  • Una combinación de varias de las anteriores.

Si migra desde el enlace público o el código de inserción, tenga en cuenta que el formulario ya no traerá esta protección. Lo más práctico es resolverlo en el mismo cambio y no después.

Lo que la plataforma sigue cubriendo:

  • La doble confirmación, cuando el formulario la exige. Es la defensa más sólida contra la suscripción de terceros: el consentimiento no rige hasta que esa persona confirma desde su propio correo, así que un robot que use la dirección de otra persona no obtiene un consentimiento válido.
  • La validación de cada valor contra la configuración de su campo.
  • Las reglas de integridad del envío descritas más abajo.
  • El descuento del cupo del plan por cada correo que genera el envío, que fija el máximo que puede producirse por esta vía.

Request​

Path params:

idintegerobligatorio
Identificador del formulario (ver `GET /v1/forms`).

Headers opcionales:

Idempotency-Keyheader
Cadena que identifica este envío. Ver Idempotencia.

Body:

{
"items": [
{ "item_id": 41, "value": "[email protected]" },
{ "item_id": 42, "value": true, "term_version_id": 7 }
],
"ip_address": "200.55.200.100",
"user_agent": "Mozilla/5.0 (...)"
}
ip_addressstring
Dirección desde la que el contacto envió el formulario, tal como la observó usted. Queda registrada como dato declarado.
user_agentstring
Cliente desde el que el contacto lo envió, tal como lo observó usted. Queda registrado como dato declarado.
itemsarrayobligatorio
Una entrada por cada campo y cada consentimiento que declara el formulario. Ver las reglas más abajo.
item_idintegerobligatorio
Identificador del ítem: el id que trae `GET /v1/forms/{id}` en items[].
valueanyobligatorio
El valor capturado. En un ítem de consentimiento es true si se marcó y false si no.
term_version_idinteger
Obligatorio en los ítems de consentimiento. La versión del término que se mostró al contacto, tal como la entrega GET /v1/forms/{id}.

El envío tiene que reflejar el formulario completo​

La plataforma no tiene acceso a lo que su sistema mostró al contacto, por lo que verifica que el envío corresponda al formulario que declara enviar. Son cuatro reglas, y cada una evita una forma distinta de registrar un envío incorrecto sin que se advierta:

ReglaSi no se cumplePor qué
Envíe todos los campos del formulario, incluidos los opcionales. Si el contacto dejó uno vacío, envíelo con value: nullfield_item_omittedOmitir un campo equivale a indicar que no se le mostró al contacto, lo que es distinto de que lo haya dejado en blanco. Un formulario que pregunta menos de lo que declara ya no es el que se publicó.
Envíe todos los consentimientos del formulario, estén marcados o no. Los no marcados van con value: falseconsent_item_omittedUn consentimiento sin marcar es un rechazo y se registra como tal. Si se omitiera, ese rechazo no quedaría en el historial.
Envíe cada item_id una sola vezitem_duplicatedCon un ítem repetido, el valor registrado dependería del orden de las entradas. Se rechaza el envío en lugar de suponer cuál quiso enviar.
Envíe solo item_id que el formulario declareitem_unknownUn ítem que el formulario no declara no se registraría, y la llamada respondería 201 sin que el valor quedara guardado. Suele indicar un envío armado para otro formulario, o para una versión de este que ya cambió.

Los separadores y los bloques de texto no se envían, porque no capturan ningún valor.

La versión del término tiene que ser la vigente​

term_version_id es la prueba de que el contacto leyó el texto que está en vigor. Si entre su última lectura del formulario y el envío se publicó una versión nueva del término, el envío se rechaza con consent_term_version_stale y la respuesta indica cuál es la vigente. Para resolverlo, consulte de nuevo GET /v1/forms/{id}, muestre al contacto el texto vigente y repita el envío.

Todos los problemas se informan juntos. Una llamada con varios errores los devuelve todos en un 422, para corregirlos de una vez y no de a uno por reintento.

Response​

201 CreatedDevuelve el envío registrado. Los campos son los que siguen.
idinteger
Identificador del envío.
statusstring, enum: `confirmed | pending`
confirmed: el envío quedó registrado y sus consentimientos rigen. pending: el formulario exige doble confirmación y se envió al contacto el correo con el enlace. Los consentimientos no rigen hasta que lo confirme.
Nota

200 OK — ya existía un envío con el mismo Idempotency-Key; no se creó nada. El cuerpo es el del envío original.

Idempotencia​

Reintentar una llamada cuya respuesta no se recibió crearía un segundo envío, un segundo registro de consentimiento y un segundo correo al contacto. Para evitarlo, envíe Idempotency-Key con una cadena que identifique el envío, por ejemplo el identificador que este tiene en su propio sistema.

  • La primera llamada responde 201 y registra el envío.
  • Un reintento del mismo envío responde 200 con el mismo id, sin crear un nuevo registro ni enviar un segundo correo. ip_address y user_agent pueden diferir, porque los observa el proceso que reintenta y no el contacto; sigue siendo el mismo envío.
  • La misma clave con un envío distinto se rechaza con 422 y code: idempotency_key_reused. Una clave repetida suele deberse a un contador que se reinició. Responder 200 con el envío anterior haría que el nuevo se perdiera sin aviso, por eso se rechaza.
  • El alcance de la clave es el formulario: dos formularios distintos pueden usar su propia numeración sin interferir.
  • Sin la cabecera, cada llamada es un envío nuevo.

La clave se conserva mientras exista el envío, sin plazo de caducidad. La clave se guarda junto con el envío, que se conserva como evidencia. Use una clave única de forma permanente, como un UUID o el identificador que ese envío tiene en su sistema, y no un contador que se reinicie.

Errores​

CódigoCuándo
403La API key no tiene el scope form:write, o la IP no está en la lista de acceso permitido.
404El formulario no existe o está desactivado (form_not_found). Las dos situaciones responden igual.
409Un consentimiento enviado no tiene en el formulario un campo que identifique al contacto en su canal (por ejemplo, un término de correo en un formulario sin campo de correo).
422El envío no cumple el formulario (validation_error). Cada falla llega en errors[] con su propio code: un valor no cumple la configuración de su campo (field_value_invalid), falta un valor obligatorio (field_value_missing), no se aceptó un consentimiento obligatorio (consent_required), falta un campo (field_item_omitted) o un consentimiento (consent_item_omitted) que el formulario declara, se repitió un ítem (item_duplicated), se mandó uno que el formulario no declara (item_unknown), o la versión del término declarada ya no es la vigente (consent_term_version_stale). Se informan todas a la vez.

Un caso del 422 tiene código propio: idempotency_key_reused. Se devuelve al reutilizar un Idempotency-Key con un envío distinto (ver Idempotencia). Su código es distinto del validation_error de los demás 422 porque la solución es otra: el cuerpo no tiene nada que corregir. Reenvíe el envío con una clave nueva.

Formato del cuerpo de error y detalle por código: Respuestas de error.

Notas
  • Los correos consumen el cupo de su plan. El correo de doble confirmación y la respuesta automática son correos salientes, y cada uno se descuenta del plan igual que cualquier otro envío.
  • ip_address y user_agent son datos declarados por usted, no observados por la plataforma. En el historial y en la exportación, estos envíos se distinguen de los hechos en una página de Fidelizador.
  • No hay alta de formularios por API. El formulario, sus campos y sus términos se configuran en el panel.
Request
curl -X POST "https://$API_HOST/v1/forms/12/submissions" \
-H "Authorization: Bearer $API_KEY" \
-H "X-Instance-Slug: $SLUG" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: alta-web-48213" \
-d '{
"items": [
{
"item_id": 41,
"value": "[email protected]"
},
{
"item_id": 42,
"value": true,
"term_version_id": 7
}
],
"ip_address": "200.55.200.100"
}'
Response
{
"id": 9814,
"status": "confirmed"
}