Orders API (generic connection)
The HTTP endpoint that records an order — or a cart someone has started to pay for — from any tool. Exact contract, field reference, responses, the 422 error catalogue and which contact level each payload creates.
On this page
The «Conexión genérica» (generic connection) is a single HTTP endpoint that turns an order from any tool — Zapier, Make, a webhooks module or your own code — into a recorded conversion and, when the phone number is recognisable, into a contact with its consent level.
You will find your exact URL in the app, under «Ajustes → Integraciones → Conexión genérica» (settings → integrations → generic connection).
The endpoint
POST https://app.konverda.com/webhooks/orders/<tu-token>
Content-Type: application/json
Payload
Only order_ref is required. Everything else is optional and improves what the platform can do with the order.
{
"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"]
}
Field reference
| Field | Type | Description |
|---|---|---|
order_ref | text or number · required | Unique reference of the order in your platform (max. 120 characters). It is the deduplication key: resending the same order_ref never duplicates the sale. |
status | 'paid' | 'refunded' | Defaults to 'paid'. 'refunded' records a refund of the same order. |
occurred_at | ISO 8601 date · optional | When it happened. Without it, the moment of receipt is used. |
total | number · optional | Total amount of the order. Numeric text is also accepted ("49.90"). |
currency | 3 letters · optional | Defaults to EUR. |
customer | object · optional | Container for the customer’s data: phone, name, email. |
customer.phone | text · optional | The customer’s WhatsApp phone number. Without it (or if it is not recognisable) the sale is recorded all the same, but with NO contact — it shows up under «Conversiones sin contacto» (conversions with no contact). |
customer.name | text · optional | The customer’s name. |
customer.email | text · optional | Accepted but NEVER stored — email lives in your email tool. It is also scrubbed from the technical log of the request. |
consent | strict boolean (true / false) · optional | The consent checkbox of your checkout. IT DECIDES THE CONTACT’S LEVEL — see Consent. Careful: a quoted "true" or a 1 is NOT accepted here; a non-boolean value returns 422 bad_consent. |
items[] | list · optional | Max. 100 items: { id, name, quantity, total }. Each one needs at least id or name. They feed the catalogue and the per-product automations. |
tags[] | list of text · optional | Max. 20 tags to apply to the contact. |
token | text · optional | The wtk parameter that sent links append to the destination URL (max. 64 characters). If the order carries no usable phone number but does carry this token, the sale is linked to the contact who received the link (it shows as «vía enlace» — via link). |
cart_ref | text or number · optional | On an order: the cart this payment closes (max. 120 characters) — the same cart_ref you sent when the checkout started. Without it, the order still closes that contact’s open carts. On a cart it is required — see Carts. |
event | 'checkout.started' · optional | Without it, the payload is an order. With "checkout.started" it is a cart someone has started to pay for — see Carts. Any other value returns 422 bad_event. |
recovery_url | https or http URL · carts only | The link that reopens that cart in your store. Ignored on an order. |
Trying it with 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 }]
}'
A correct response is {"ok":true,"matched":true}. The sale appears under «Ajustes → Integraciones → Catálogo» (settings → integrations → catalogue) and, if the phone number is recognisable, on the contact’s page.
Responses
| Code | Body | Meaning |
|---|---|---|
200 | {"ok":true,"matched":true} | Recorded and linked to a contact. |
200 | {"ok":true,"matched":false} | Recorded with NO contact (phone number absent or unrecognisable) — visible under «Conversiones sin contacto» (conversions with no contact). |
200 | {"ok":true,"duplicate":true} | That order_ref was already recorded. Resending is safe. |
200 | {"ok":true,"recorded":false,"reason":"no_consent"} | consent:false and the phone number does not belong to an already registered contact — no profile is created. It is a 200 on purpose: retrying would never change the outcome. |
404 | {"error":"not_found"} | Unknown URL or deactivated connection. Both answer the same: a 404 does not reveal whether the token exists. |
422 | {"error":"invalid_payload","details":[…]} | Invalid payload; details carries the per-field error codes. |
429 | {"error":"rate_limited"} | More than 60 requests per minute per IP — wait and retry. |
500 | {"error":"ingest_error"} | Internal error; retry later (deduplication by order_ref makes the retry safe). |
The 422 error catalogue
details is a list of codes, one per problem found. The whole request is rejected: nothing is recorded partially.
| Code | What causes it |
|---|---|
missing_order_ref | order_ref is missing (or arrives as null). |
bad_order_ref | order_ref is neither text nor number, is empty, or is longer than 120 characters. |
bad_status | status is neither 'paid' nor 'refunded'. |
bad_occurred_at | occurred_at is not text, or is not an interpretable date. |
bad_total | total cannot be read as a number. |
bad_currency | currency is not exactly 3 letters. |
bad_customer | customer is not an object (it arrived as text or as a list). |
bad_consent | consent is not a boolean. "true", "1" and 1 are rejected here on purpose: consent is not guessed. |
bad_items | items is not a list, is longer than 100, or some item carries neither id nor name (or carries a non-numeric quantity/total). |
bad_tags | tags is not a list of text, or is longer than 20. |
bad_token | token is not text, or is longer than 64 characters. |
Consent: which contact level each payload creates
Since 25 August 2026 a contact has TWO possible levels, and the consent field of your payload decides which one.
- «Marketing» = they agreed to receive messages: campaigns, waves, proactive automations and 1 templates.
- «No» = we can only reply to them when they write.
This is not a reporting nuance: a contact at level «No» receives NO message started by us — not even a utility template, not even the welcome automation. The attempt is logged as not_marketing and nothing is sent.
| What you send | Basis recorded | Resulting opt-in | What they can receive |
|---|---|---|---|
consent: true | checkbox | «Marketing» | Everything: campaigns, waves, proactive automations (order.paid included) and 1 templates. And, being an explicit act by the customer themselves, it lifts a previous opt-out. |
consent absent | purchase | «No» | Only replies when the person writes. The order.paid automation is NOT sent. A previous opt-out is always respected. |
consent: false | — (no registration recorded) | Unchanged | The sale is recorded ONLY if the phone number was already a registered contact; if not, no profile is created and the response says so (recorded:false). |
Carts: checkout.started
The same URL also records a cart: the moment someone starts to pay in your store. If they do not finish the purchase within the time you set in «Ajustes → Reglas» (settings → rules; 45 minutes unless you change it), Konverda treats the cart as abandoned and the flow you built on the «Carrito abandonado» (abandoned cart) trigger starts. What the customer sees, the consent it requires and the safeguards are in Abandoned cart.
{
"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 }
]
}
Cart fields
| Field | Type | Description |
|---|---|---|
event | 'checkout.started' · required | What turns the payload into a cart. |
cart_ref | text or number · required | The cart’s id in your store (max. 120 characters). Resending the same cart_ref updates that cart and restarts its countdown: it never creates a second cart or a second reminder. |
recovery_url | https or http URL · required | The link that reopens the cart (max. 2048 characters). Konverda never sends it as is: it turns it into a tracked link, with its UTMs. |
customer.phone | text · optional | Used to find the contact. Same format as on orders. |
customer.email | text · optional | Used to find the contact when the phone is not enough. On a cart with no contact it is kept only while the cart is open — the checkbox can arrive a moment later — and deleted as soon as the cart closes. |
items[] | list · optional | As on orders. The first named item and the number of units can go into the message. |
total | number · optional | The cart’s value. It can go into the message in your currency’s format («79,90 €»). |
currency | 3 letters · optional | Defaults to EUR. |
occurred_at | ISO 8601 date · optional | When the checkout started. The countdown starts when we receive it, not at this date. |
status, consent, token and tags are ignored on a cart.
Cart responses
| Code | Body | Meaning |
|---|---|---|
200 | {"ok":true,"matched":true,"armed":true} | Cart recorded, contact found, countdown running. |
200 | {"ok":true,"matched":false,"armed":true} | Recorded with no contact yet. We look again when the countdown ends; if there is still nobody, the cart closes with no message. |
200 | {"ok":true,"matched":…,"armed":false} | That cart was already closed (bought, reminded or dropped). The resend changes nothing. |
404, 429 and 500 mean the same as for orders.
Cart errors
| Code | What causes it |
|---|---|
bad_event | event carries a value other than "checkout.started". |
missing_cart_ref | cart_ref is missing. |
bad_cart_ref | cart_ref is not text or a number, is empty, or is longer than 120 characters. |
missing_recovery_url | recovery_url is missing. |
bad_recovery_url | recovery_url is not a valid https/http address or is longer than 2048 characters. |
bad_occurred_at | occurred_at is not a readable date. |
bad_total | total cannot be read as a number. |
bad_currency | currency is not exactly 3 letters. |
bad_customer | customer is not an object. |
bad_items | items is not a valid list (same rules as on orders). |
Closing the cart on payment
When the purchase completes, send the normal order to this same URL. If it carries the cart’s cart_ref, it closes exactly that cart; if not, it closes that contact’s open carts. On top of that, right before sending the reminder, Konverda checks whether the person has bought since the cart started — through any connection, Stripe or WooCommerce included — and if they have, nothing is sent.
Read next
- The marketing opt-in and «Añadir a marketing» — what the Opt-in column means, who is included in a campaign, and how to declare consent you hold outside the platform.
- Opt-outs and what the platform remembers — an opt-out is stored against the number, survives deletion, and only the contact themselves can lift it.
- Connecting through Zapier or Make — the same contract, without writing code.
- Stores and payment gateways — recipes for PrestaShop, Wix, Squarespace, Shopify and Stripe.
- Abandoned cart — the checkout checkbox, the countdown, the safeguards and the flow that sends the reminder.
Still stuck? Contact support