API de pedidos (conexión genérica)
El endpoint HTTP que registra un pedido —o un carrito que alguien empieza a pagar— desde cualquier herramienta. Contrato exacto, referencia de campos, respuestas, catálogo de errores 422 y qué nivel de contacto crea cada payload.
En esta página
La conexión genérica es un solo endpoint HTTP que convierte un pedido de cualquier herramienta —Zapier, Make, un módulo de webhooks o tu propio código— en una conversión registrada y, cuando el teléfono es reconocible, en un contacto con su nivel de consentimiento.
Encontrarás tu URL exacta en la app, en Ajustes → Integraciones → Conexión genérica.
El endpoint
POST https://app.konverda.com/webhooks/orders/<tu-token>
Content-Type: application/json
Payload
Solo order_ref es obligatorio. Todo lo demás es opcional y mejora lo que la plataforma puede hacer con el pedido.
{
"order_ref": "pedido-1234",
"status": "paid",
"occurred_at": "2026-07-12T10:00:00Z",
"total": 49.9,
"currency": "EUR",
"customer": {
"phone": "+34 612 345 678",
"name": "Ana Pérez",
"email": "ana@example.com"
},
"consent": true,
"items": [
{ "id": "SKU-9", "name": "Curso X", "quantity": 1, "total": 49.9 }
],
"tags": ["cliente"]
}
Referencia de campos
| Campo | Tipo | Descripción |
|---|---|---|
order_ref | texto o número · obligatorio | Referencia única del pedido en tu plataforma (máx. 120 caracteres). Es la clave de deduplicación: reenviar el mismo order_ref nunca duplica la venta. |
status | 'paid' | 'refunded' | Por defecto 'paid'. 'refunded' registra un reembolso del mismo pedido. |
occurred_at | fecha ISO 8601 · opcional | Cuándo ocurrió. Sin él se usa el momento de recepción. |
total | número · opcional | Importe total del pedido. Acepta también texto numérico ("49.90"). |
currency | 3 letras · opcional | Por defecto EUR. |
customer | objeto · opcional | Contenedor de los datos del cliente: phone, name, email. |
customer.phone | texto · opcional | Teléfono WhatsApp del cliente. Sin él (o si no es reconocible) la venta se registra igualmente, pero SIN contacto — aparece en «Conversiones sin contacto». |
customer.name | texto · opcional | Nombre del cliente. |
customer.email | texto · opcional | Se acepta pero NUNCA se guarda — el email vive en tu herramienta de email. Se borra también del registro técnico de la petición. |
consent | booleano estricto (true / false) · opcional | La casilla de consentimiento de tu checkout. DECIDE EL NIVEL DEL CONTACTO — ver Consentimiento. Ojo: aquí NO se acepta "true" entre comillas ni 1; un valor que no sea booleano devuelve 422 bad_consent. |
items[] | lista · opcional | Máx. 100 artículos: { id, name, quantity, total }. Cada uno necesita al menos id o name. Alimentan el Catálogo y las automatizaciones por producto. |
tags[] | lista de texto · opcional | Máx. 20 etiquetas a aplicar al contacto. |
token | texto · opcional | El parámetro wtk que los enlaces enviados añaden a la URL de destino (máx. 64 caracteres). Si el pedido no trae teléfono utilizable pero sí este token, la venta se vincula al contacto que recibió el enlace (aparece como «vía enlace»). |
cart_ref | texto o número · opcional | En un pedido: el carrito que este pago cierra (máx. 120 caracteres), el mismo cart_ref que enviaste al empezar el pago. Sin él, el pedido cierra igualmente los carritos abiertos de ese contacto. En un carrito es obligatorio — ver Carritos. |
event | 'checkout.started' · opcional | Sin él, el payload es un pedido. Con "checkout.started" es un carrito que alguien ha empezado a pagar — ver Carritos. Cualquier otro valor devuelve 422 bad_event. |
recovery_url | URL https o http · solo en carritos | El enlace que reabre ese carrito en tu tienda. En un pedido se ignora. |
Probarlo con curl
curl -X POST 'https://app.konverda.com/webhooks/orders/<tu-token>' \
-H 'Content-Type: application/json' \
-d '{
"order_ref": "prueba-1",
"total": 49.9,
"customer": { "phone": "+34612345678", "name": "Ana Pérez" },
"consent": true,
"items": [{ "id": "SKU-9", "name": "Curso X", "quantity": 1, "total": 49.9 }]
}'
Una respuesta correcta es {"ok":true,"matched":true}. La venta aparece en Ajustes → Integraciones → Catálogo y, si el teléfono es reconocible, en la ficha del contacto.
Respuestas
| Código | Cuerpo | Significado |
|---|---|---|
200 | {"ok":true,"matched":true} | Registrado y vinculado a un contacto. |
200 | {"ok":true,"matched":false} | Registrado SIN contacto (teléfono ausente o irreconocible) — visible en «Conversiones sin contacto». |
200 | {"ok":true,"duplicate":true} | Ese order_ref ya estaba registrado. Reenviar es seguro. |
200 | {"ok":true,"recorded":false,"reason":"no_consent"} | consent:false y el teléfono no corresponde a un contacto ya dado de alta — no se crea ningún perfil. Es un 200 a propósito: reintentar nunca cambiaría el resultado. |
404 | {"error":"not_found"} | URL desconocida o conexión desactivada. Ambas responden igual: un 404 no revela si el token existe. |
422 | {"error":"invalid_payload","details":[…]} | Payload inválido; details lleva los códigos de error por campo. |
429 | {"error":"rate_limited"} | Más de 60 peticiones por minuto y por IP — espera y reintenta. |
500 | {"error":"ingest_error"} | Error interno; reintenta más tarde (la deduplicación por order_ref hace el reintento seguro). |
Catálogo de errores del 422
details es una lista de códigos, uno por problema encontrado. La petición se rechaza entera: no se registra nada parcialmente.
| Código | Qué lo provoca |
|---|---|
missing_order_ref | Falta order_ref (o llega null). |
bad_order_ref | order_ref no es texto ni número, está vacío o pasa de 120 caracteres. |
bad_status | status no es 'paid' ni 'refunded'. |
bad_occurred_at | occurred_at no es texto o no es una fecha interpretable. |
bad_total | total no se puede leer como número. |
bad_currency | currency no son exactamente 3 letras. |
bad_customer | customer no es un objeto (llegó como texto o como lista). |
bad_consent | consent no es booleano. "true", "1" y 1 se rechazan aquí a propósito: el consentimiento no se adivina. |
bad_items | items no es una lista, pasa de 100, o algún artículo no trae ni id ni name (o trae quantity/total no numéricos). |
bad_tags | tags no es una lista de textos o pasa de 20. |
bad_token | token no es texto o pasa de 64 caracteres. |
Consentimiento: qué nivel de contacto crea cada payload
Desde el 25 de agosto de 2026 un contacto tiene DOS niveles posibles, y el campo consent de tu payload decide cuál.
- «Marketing» = aceptó recibir envíos: campañas, oleadas, automatizaciones proactivas y plantillas 1.
- «No» = solo podemos responderle cuando escribe él.
No es un matiz de informe: un contacto de nivel «No» no recibe NINGÚN mensaje que iniciemos nosotros — ni siquiera una plantilla de utilidad, ni la automatización de bienvenida. El intento queda registrado como not_marketing y no se envía.
| Lo que envías | Base registrada | Opt-in resultante | Qué puede recibir |
|---|---|---|---|
consent: true | checkbox | Marketing | Todo: campañas, oleadas, automatizaciones proactivas (order.paid incluida) y plantillas 1. Además, al ser un acto explícito del propio cliente, levanta una baja anterior. |
consent ausente | purchase | No | Solo respuestas cuando la persona escribe. La automatización de order.paid NO se envía. Una baja anterior se respeta siempre. |
consent: false | — (no se registra alta) | Sin cambios | La venta se registra SOLO si el teléfono ya era un contacto dado de alta; si no, no se crea ningún perfil y la respuesta lo dice (recorded:false). |
Carritos: checkout.started
La misma URL registra también un carrito: el momento en que alguien empieza a pagar en tu tienda. Si no termina la compra en el tiempo que marques en Ajustes → Reglas (45 minutos si no lo cambias), Konverda lo da por abandonado y se inicia el flujo que tengas con el disparador Carrito abandonado. Lo que ve el cliente, el consentimiento que exige y las protecciones están en Carrito abandonado.
{
"event": "checkout.started",
"cart_ref": "carrito-5678",
"recovery_url": "https://tu-tienda.com/carrito/recuperar/5678",
"occurred_at": "2026-09-11T10:00:00Z",
"total": 79.9,
"currency": "EUR",
"customer": {
"phone": "+34 612 345 678",
"email": "ana@example.com"
},
"items": [
{ "id": "SKU-9", "name": "Curso X", "quantity": 1, "total": 49.9 },
{ "id": "SKU-12", "name": "Libro del curso", "quantity": 1, "total": 30 }
]
}
Campos del carrito
| Campo | Tipo | Descripción |
|---|---|---|
event | 'checkout.started' · obligatorio | Lo que convierte el payload en un carrito. |
cart_ref | texto o número · obligatorio | El identificador del carrito en tu tienda (máx. 120 caracteres). Reenviar el mismo cart_ref actualiza ese carrito y vuelve a empezar su cuenta atrás: nunca crea un segundo carrito ni un segundo aviso. |
recovery_url | URL https o http · obligatorio | El enlace que reabre el carrito (máx. 2048 caracteres). Konverda nunca lo envía tal cual: lo convierte en un enlace con seguimiento, con sus UTM. |
customer.phone | texto · opcional | Para encontrar al contacto. Mismo formato que en los pedidos. |
customer.email | texto · opcional | Para encontrar al contacto si el teléfono no basta. En un carrito sin contacto se guarda solo mientras el carrito está abierto —la casilla puede llegar un instante después— y se borra en cuanto se cierra. |
items[] | lista · opcional | Como en los pedidos. El primer artículo con nombre y el número de unidades se pueden poner en el mensaje. |
total | número · opcional | El importe del carrito. Se puede poner en el mensaje con el formato de tu moneda («79,90 €»). |
currency | 3 letras · opcional | Por defecto EUR. |
occurred_at | fecha ISO 8601 · opcional | Cuándo empezó el pago. La cuenta atrás empieza al recibirlo, no en esta fecha. |
status, consent, token y tags se ignoran en un carrito.
Respuestas del carrito
| Código | Cuerpo | Significado |
|---|---|---|
200 | {"ok":true,"matched":true,"armed":true} | Carrito registrado, contacto encontrado, cuenta atrás en marcha. |
200 | {"ok":true,"matched":false,"armed":true} | Registrado sin contacto todavía. Se vuelve a buscar cuando termina la cuenta atrás; si sigue sin haber nadie, el carrito se cierra sin mensaje. |
200 | {"ok":true,"matched":…,"armed":false} | Ese carrito ya estaba cerrado (comprado, recordado o descartado). El reenvío no cambia nada. |
404, 429 y 500 significan lo mismo que en los pedidos.
Errores del carrito
| Código | Qué lo provoca |
|---|---|
bad_event | event trae un valor que no es "checkout.started". |
missing_cart_ref | Falta cart_ref. |
bad_cart_ref | cart_ref no es texto ni número, está vacío o pasa de 120 caracteres. |
missing_recovery_url | Falta recovery_url. |
bad_recovery_url | recovery_url no es una dirección https/http válida o pasa de 2048 caracteres. |
bad_occurred_at | occurred_at no es una fecha interpretable. |
bad_total | total no se puede leer como número. |
bad_currency | currency no son exactamente 3 letras. |
bad_customer | customer no es un objeto. |
bad_items | items no es una lista válida (mismas reglas que en los pedidos). |
Cerrar el carrito al pagar
Cuando la compra se completa, envía el pedido normal a esta misma URL. Si lleva el cart_ref del carrito, cierra ese carrito exactamente; si no, cierra los carritos abiertos de ese contacto. Además, justo antes de enviar el recordatorio, Konverda comprueba si esa persona ha comprado desde que empezó el carrito —por cualquier conexión, también Stripe o WooCommerce—, y si ha comprado no envía nada.
Seguir leyendo
- El opt-in de marketing y «Añadir a marketing» — qué significa la columna Opt-in, quién entra en una campaña y cómo declarar un consentimiento que tienes fuera de la plataforma.
- La baja y qué recuerda la plataforma — la baja se guarda contra el número, sobrevive a borrados y solo el propio contacto puede levantarla.
- Conectar vía Zapier o Make — el mismo contrato, sin escribir código.
- Tiendas y pasarelas — recetas para PrestaShop, Wix, Squarespace, Shopify y Stripe.
- Carrito abandonado — la casilla del checkout, la cuenta atrás, las protecciones y el flujo que envía el recordatorio.
¿No lo has resuelto? Contactar soporte