Crear sobre Conecto
Una API REST clara para todo lo que hace la plataforma: cree bots que no solo respondan, sino que puedan actuar , en su base de datos, facturación y producto, envíe imágenes, GIF, vídeos y tarjetas al chat, y crear sus propias integraciones que el agente de IA llama durante la conversación, sea cual sea el software subyacente. Si el widget puede hacerlo, la API puede controlarlo.
En producción · Webhooks firmados · 300 solicitudes/min
Conceptos básicos
Base URL https://conecto.chat/api/v1. Create a credential in Ajustes → Desarrolladores , obtiene un ID de cliente y un secreto (shown once, stored hashed). Authenticate with HTTP Basic — client ID as username, secret as password — or Authorization: Bearer <client_id>:<secret>. A credential can cover the whole workspace or be scoped to a single widget.
curl https://conecto.chat/api/v1/me/ -u "ck_your_client_id:cs_your_secret"¿Usa Python? El SDK oficial (pip install conecto) wraps all of this, and does the parts that are easy to get subtly wrong for you: webhook signature verification, delivery deduplication, idempotent writes, retries, and block validation that fires before a request leaves your process. Everything below still applies — it is the same API underneath.
Límite de frecuencia 300 solicitudes/min per credential (429 + Retry-After beyond it). Errors are always {"error": {"code", "message"}}. List endpoints paginate with limit y before_id, returning next_before_id. Write endpoints accept an Idempotency-Key header (any UUID): retry the same key and you get the message that was already created, with a 200 instead of a 201, rather than a second copy in front of the visitor.
Un endpoint describe todos los demás
GET /schema/ devuelve toda la superficie como JSON: cada endpoint, evento, tipo de bloque, acción reservada, código de error y límite numérico. Lo sirve el mismo código que aplica esos límites, por lo que no puede apartarse del servidor activo como sí puede hacerlo una página de documentación. Genere su cliente a partir de ahí o compruebe al iniciar que la función necesaria está desplegada.
curl https://conecto.chat/api/v1/schema/ -u "ck_...:cs_..." | jq '.limits, .events[].name'Every response carries X-Conecto-Api-Version. Pin it in your client and log a warning when it changes — that header is how you find out a field moved before your users do.
Contenido enriquecido: imágenes, GIF, vídeo y tarjetas
Any message you send can carry blocks — an ordered list of typed content rendered under the bubble in the widget, and in the agent's inbox exactly as the visitor saw it. Up to 10 per message, and every field is validated and re-projected server-side, so you never hand us markup and we never hand the browser a string.
curl -X POST https://conecto.chat/api/v1/conversations/42/messages/ \
-u "ck_...:cs_..." -H "Content-Type: application/json" \
-d '{
"body": "Here is how to reset it — takes about 20 seconds:",
"blocks": [
{"type": "image", "url": "https://cdn.you.com/reset.gif", "alt": "Reset flow"},
{"type": "buttons", "items": [
{"label": "That worked"},
{"label": "Full guide", "url": "https://docs.you.com/reset"}
]}
]
}'Tipos de bloques
imageurl, alt, caption, link — animated GIFs are just imagesvideourl to an .mp4/.webm/.mov, plus poster, autoplay, loop, mutedembedprovider youtube · vimeo · loom · wistia · spotify y el enlace normal para compartir urlaudiourl a un archivo .mp3/.wav/.m4a, opcional titlefileurl, filename, size — rendered as a download rowcardsitems[] of title · subtitle · text · image · url · price · badge · buttons; layout carousel or listlistrows[] de etiqueta · valor · subtítulo · imagen · URL, para resúmenes de pedidos y pasos de envíobuttonsitems[]: {label, value} replies as the visitor, {label, url} opens a linktext · dividerpárrafos adicionales y una línea horizontalURLs must be https. A block that can't be built is a 400 naming the block and the reason, never a silent drop — an image that vanishes without explanation costs an afternoon to track down.
Enviar un GIF que no tiene dónde alojar
POST /media/ takes a multipart file (≤10 MB) and hands back a URL you can drop straight into a block. Use the returned url — it is a path, resolved against the widget's own origin, so the same message works in development and in production.
URL=$(curl -s -X POST https://conecto.chat/api/v1/media/ -u "ck_...:cs_..." \
-F "file=@celebrate.gif" | jq -r .media.url)
curl -X POST https://conecto.chat/api/v1/conversations/42/messages/ \
-u "ck_...:cs_..." -H "Content-Type: application/json" \
-d "{\"body\": \"All set!\", \"blocks\": [{\"type\": \"image\", \"url\": \"$URL\"}]}"Shipping a GIF as a muted, looping video block is usually the better trade: a tenth of the bytes, and the visitor cannot tell the difference.
Un carrusel de tarjetas
{"blocks": [{
"type": "cards",
"items": [
{"title": "Trail Runner 2", "subtitle": "Road · Neutral",
"price": "89.00 USD", "badge": "Back in stock",
"image": "https://cdn.you.com/tr2.jpg",
"url": "https://shop.you.com/p/tr2",
"buttons": [{"label": "Add to cart", "url": "https://shop.you.com/cart/add/tr2"}]}
]
}]}Cree su propia integración
Shopify, Stripe y BigCommerce son integraciones que nosotros hemos creado. Esta es la que usted crear. Describa su servicio con una URL base y una lista de acciones, instálelo en un widget y el agente de IA lo llamará durante la conversación. Los sistemas posteriores no pueden distinguirlo de una integración nativa. Ese es precisamente el objetivo: si su tienda, facturación o CRM funciona con algo que no conocemos, ya no tiene que esperar a que lo admitamos.
1 · Registrarla
curl -X POST https://conecto.chat/api/v1/integrations/ \
-u "ck_...:cs_..." -H "Content-Type: application/json" \
-d '{
"slug": "acme-store",
"name": "Acme Store",
"base_url": "https://api.acme.example/conecto",
"auth_type": "bearer",
"credential": "sk_live_...",
"actions": [
{"name": "catalog.search_products", "path": "/search",
"description": "Search the Acme catalog by keyword.",
"parameters": [{"name": "query", "type": "string", "required": true},
{"name": "limit", "type": "integer"}]},
{"name": "orders.get_status", "path": "/orders/status", "risk": "verified_read",
"description": "Look up one of the visitor's orders by number.",
"parameters": [{"name": "order_number", "type": "string", "required": true}]},
{"name": "warranty.register", "path": "/warranty", "risk": "write",
"description": "Register a warranty for a product the visitor owns.",
"parameters": [{"name": "serial", "type": "string", "required": true}]}
]
}'The response includes a signing_secret. You need it to verify our calls — it is readable on every later GET too, and {"rotate_signing_secret": true} rolls it.
2 · Instalarla en un widget
curl -X POST https://conecto.chat/api/v1/integrations/acme-store/install/ \
-u "ck_...:cs_..." -H "Content-Type: application/json" \
-d '{"widget_ids": [7], "actions": ["catalog.search_products", "orders.get_status"]}'actions is an allowlist — omit it to enable everything you declared, or pass [] to keep the integration installed but idle. Nothing is exposed until you install it, and installing is per widget.
3 · Responder a la llamada
We POST a signed JSON envelope to base_url + path. Verify the signature the same way you verify a webhook — one routine covers both directions:
// POST https://api.acme.example/conecto/search
{
"action": "catalog.search_products",
"integration": "acme-store",
"arguments": { "query": "trail shoes", "limit": 3 },
"source": "ai_agent",
"idempotency_key": "8f14e45fceea167a...",
"workspace": { "id": 12 },
"widget": { "id": 7, "key": "w_..." },
"conversation": { "id": 4821 },
"visitor": { "session": "…", "email": "maya@acme.io",
"verified": true, "verified_email": "maya@acme.io", "name": "Maya" }
}
// Headers: X-Conecto-Signature: sha256=<HMAC-SHA256(signing_secret, raw body)>
// X-Conecto-Timestamp, X-Conecto-Action, X-Conecto-Integration,
// X-Conecto-Idempotency-KeyResponder con una de cinco estructuras:
{"ok": true, "result": {...}} // success — the agent reads it
{"ok": true, "result": {...}, "blocks": [...]} // …and attach rich content to the reply
{"ok": true, "not_found": true} // nothing matched (a normal outcome)
{"verify_required": true} // "I need a verified visitor for this"
{"ok": false, "error": "Already cancelled."} // a failure the agent may relayUn objeto JSON sencillo sin ninguna de esas claves se considera el propio resultado. Así puede dirigir una acción a un endpoint que ya tenga. Límites: 8 s tiempo de espera, 128 KB de respuesta. Todo lo que devuelve llega al modelo como datos dentro de una envoltura JSON, nunca como instrucciones. Las claves que parecen secretos se eliminan al entrar.
4 · Probarlo antes que un visitante
curl -X POST https://conecto.chat/api/v1/integrations/acme-store/actions/catalog.search_products/run/ \
-u "ck_...:cs_..." -H "Content-Type: application/json" \
-d '{"arguments": {"query": "trail shoes"}}'This runs the real call path — signature, credential, timeout, parsing — and shows you the envelope the agent will get. Pass conversation_id to run it in the context of a live chat, which is the only way a verified action can succeed.
Niveles de riesgo y la única regla que no se puede desactivar
public_readcatálogo, disponibilidad, documentación, no requiere identidadverified_readdatos de una persona. Se rechaza hasta verificar el correo del visitantepublic_writeuna modificación que no requiere identidad, como suscripción al boletín o captura de leadswriteuna modificación en la cuenta de una persona. Verificada y nunca reintentada silenciosamentePara los dos últimos, la dirección verificada la proporciona nosotros, in visitor.verified_email — never taken from the model's arguments. Verification comes from an emailed code, or from your own site validación de un usuario conectado. Ningún ajuste del widget puede eximir una acción de esta regla, porque «¿de quién es este pedido?» no es una pregunta que deba responder un interruptor.
Acciones reservadas: su tienda, conectada como una integración nativa
A few action names mean something to the platform itself. Declare catalog.search_products returning {"products": [{title, url, image, price_from, currency, available}]} and its results become product cards under the AI's replies and fill the widget's Home showcase — the same treatment a Shopify connection gets, from whatever your catalog actually runs on. orders.get_status, orders.get_tracking, orders.list_recent y account.lookup are reserved the same way, and are always verified reads. GET /integrations/{slug}/actions/ returns the full catalog with the shape each one expects.
Las integraciones personalizadas forman parte de los planes de Agente de IA y están limitadas a 20 por espacio de trabajo, con 40 acciones cada una. Las llamadas salientes solo usan HTTPS, se resuelven y fijan a una IP pública antes de conectarse, y se rechazan las redirecciones. Por tanto, una URL de integración nunca puede apuntar a un destino privado.
Inicio rápido: un bot personalizado en tres pasos
A bot is a webhook and a reply. Subscribe to message.created, answer through the messages endpoint, and the widget renders it live — quick-reply buttons, email capture, the native ticket form, typing indicators. Turn the built-in AI off on the widget and your bot owns the conversation.
1. Suscribir su servidor (Ajustes → Desarrolladores → Webhooks, o mediante API):
curl -X POST https://conecto.chat/api/v1/webhooks/ \
-u "ck_...:cs_..." -H "Content-Type: application/json" \
-d '{"url": "https://bots.yourapp.com/conecto",
"events": ["message.created", "conversation.created"]}'2. Verificar y leer el evento. Every delivery is signed: X-Conecto-Signature: sha256=<HMAC-SHA256(secret, raw body)>.
// Node/Express
app.post('/conecto', express.raw({ type: '*/*' }), (req, res) => {
const sig = 'sha256=' + crypto.createHmac('sha256', WEBHOOK_SECRET)
.update(req.body).digest('hex')
if (sig !== req.get('X-Conecto-Signature')) return res.sendStatus(401)
const { event, data } = JSON.parse(req.body)
if (event === 'message.created' && data.message.sender === 'visitor') {
handle(data) // your bot logic — reply async, respond 200 fast
}
res.sendStatus(200)
})3. Responder con funciones.
curl -X POST https://conecto.chat/api/v1/conversations/42/messages/ \
-u "ck_...:cs_..." -H "Content-Type: application/json" \
-d '{"body": "I can refund order #1284 right away — confirm?",
"buttons": ["Yes, refund it", "Talk to a human"]}'Button taps come back to your webhook as normal visitor messages containing the button text — your bot's state machine lives entirely on your side. Other reply powers: blocks sends images, GIFs, video and cards (see Contenido enriquecido), {"ask_email": true} renders the email-capture form, {"ticket_form": true} attaches the native open-a-ticket form, products renders tappable product cards, {"internal": true} leaves an agent-only note, /typing/ shows “…is typing”, /handoff/ summons a human, and PATCH closes the conversation when you're done.
Recetario de bots
Recetas que realmente se llevan a producción. Cada una es un controlador de webhook más unas pocas llamadas a la API.
1 · El bot de acciones, modifica elementos en su software
Como el webhook llega a su puede hacer en su servidor todo lo que puede hacer su backend: escribir en su base de datos, llamar al sistema de facturación o actualizar un registro en su software contable. Combinado con identidad validada usted sabe exactamente quién está preguntando. Por eso «archiva este recibo de 250 $ en Marketing» se puede ejecutar de forma segura:
async function handle({ conversation, visitor, message }) {
const convo = conversation.id
// "file 250 under Marketing" — your parsing, your rules
const cmd = parseAccountingCommand(message.body)
if (!cmd) return reply(convo, "Tell me e.g. 'file 250 under Marketing'.")
// Only act for users YOUR backend has vouched for (logged in on your site)
if (!visitor.verified)
return reply(convo, "Please sign in first, then ask me again.", ["Log in"])
await ledger.addEntry({ // <- YOUR accounting system
account: cmd.account,
amount: cmd.amount,
user: visitor.verified_email, // identity Conecto guarantees
})
await reply(convo,
`Done — $${cmd.amount} filed under ${cmd.account}. Anything else?`,
["Show this month's entries", "Undo that"])
}El mismo patrón sirve para «añadir una plaza a mi plan», «cambiar el nombre de mi proyecto» o «reservar una cita el jueves». El bot es una fina capa conversacional sobre su propia API. ¿Prefiere la IA integrada en lugar de escribir un bot? Registrar los mismos endpoints como integración y la IA de Conecto las llama durante la conversación con la misma verificación de identidad, sin una máquina de estados que mantener. Si ya utiliza MCP, también sirve un servidor remoto de herramientas MCP: Panel → Agentes de IA → Integraciones.
2 · Bot de estado de pedidos
if (/where.*order|track/i.test(message.body)) {
await typing(convo, 'OrderBot', true)
const order = await shop.lastOrder(visitor.email) // your store
await reply(convo, order
? `Order #${order.id} is ${order.state} — arriving ${order.eta}.`
: "I couldn't find an order for this email.",
order ? [`Track #${order.id}`] : undefined)
}3 · Bot de desvío de tickets
Buscar primero en su centro de ayuda; abrir un ticket solo si no hay coincidencias:
const { articles } = await api('GET', '/articles/?q=' + q + '&published=true')
if (articles.length)
await reply(convo, `This might help: “${articles[0].title}”. Did that solve it?`,
["Solved it", "Open a ticket"])
else
await api('POST', `/conversations/${convo}/messages/`,
{ body: "Let's get this to the team.", ticket_form: true })4 · Bot de calificación de leads
// no email yet? capture it natively, then enrich + route
if (!visitor.email)
return api('POST', `/conversations/${convo}/messages/`,
{ body: "Happy to help — what's your work email?", ask_email: true })
await api('POST', '/contacts/', { email: visitor.email,
custom_fields: { lead_source: 'chat', intent: classify(message.body) } })
await api('POST', `/conversations/${convo}/messages/`,
{ body: "Routing you to sales…", internal: true })
await api('POST', `/conversations/${convo}/assign/`, { user_id: SALES_USER_ID })
await api('POST', `/conversations/${convo}/handoff/`)5 · Mensajes proactivos del ciclo de vida
Enviar a una sesión de visitante sin esperar a que escriba, por ejemplo novedades de envío, avisos de prueba o recuperación del carrito:
curl -X POST https://conecto.chat/api/v1/widgets/7/visitors/$SESSION/message/ \
-u "ck_...:cs_..." -H "Content-Type: application/json" \
-d '{"body": "Your order shipped — want live tracking?",
"buttons": ["Track my order"]}'Cart recovery works the same way, with products putting the item itself back in front of them as a tappable card:
-d '{"body": "Still thinking it over? Your cart is saved:",
"products": [{"title": "Trail Runner 2", "price_from": "89.00",
"currency": "USD", "image": "https://cdn.you.com/tr2.jpg",
"url": "https://shop.you.com/cart"}]}'6 · Una tienda desconocida, conectada a la IA
Sin webhook ni máquina de estados: declare la acción reservada de catálogo, apúntela a su endpoint de búsqueda y la IA recomendará productos de su catálogo con tarjetas reales.
// POST /conecto/search on your server
app.post('/conecto/search', verifyConectoSignature, async (req, res) => {
const { query, limit = 4 } = req.body.arguments
const hits = await catalog.search(query, { limit }) // <- YOUR catalog
res.json({ ok: hits.length > 0, not_found: hits.length === 0,
products: hits.map(p => ({
title: p.name, url: p.permalink, image: p.thumbnail,
price_from: p.price.toFixed(2), currency: p.currency,
available: p.stock > 0,
})) })
})Esa es toda la integración. Las tarjetas, la selección Inicio, la norma de «volver a llamar antes de mencionar un producto» y la regla que impide al modelo pegar URL sin procesar vienen incluidas con el nombre reservado.
7 · Seguimiento de CSAT
Subscribe to conversation.rated; thank promoters, rescue detractors:
if (event === 'conversation.rated') {
if (data.rating.score <= 2)
await api('POST', '/tickets/', { email: data.visitor.email,
message: `Low CSAT (${data.rating.score}/5): ${data.rating.comment}`,
priority: 'high' })
else
await push(data.widget.id, data.visitor.session,
"Glad we could help! Here's 10% off your next order: THANKS10")
}Actuar de forma segura en sus propios sistemas
Tres reglas hacen que los bots de acciones estén listos para producción:
1. Primero, la identidad. Only mutate data for sessions where visitor.verified is true — your backend vouched for them via /identify/. The vouch is time-boxed and session-scoped; revoke it with /unverify/ on logout.
2. Confirmar los pasos destructivos. Enviar la acción como pregunta con botones («¿Reembolsar el pedido n.º 1284?» · Sí / No), el toque vuelve como texto del botón, se ejecuta su controlador idempotente y la transcripción documenta el consentimiento.
3. Transferir a una persona en caso de duda. POST /conversations/{id}/handoff/ flags the thread human-needed (your routing rules apply), and {"internal": true} notes give the teammate full context your bot gathered.
Referencia de endpoints
Conversaciones y mensajes
/conversations/Newest first. Filters: status (open · pending · closed), widget_id, session; paginate with limit/before_id.
/conversations/{id}/Conversation + visitor-facing transcript (≤500 messages, since_id for increments).
/conversations/{id}/messages/body (≤4000), buttons (≤6 × 60 chars), blocks (≤10 — see Contenido enriquecido), ask_email, ticket_form (native ticket form), internal (agent-only note), products (≤4 cards rendered as a mini carousel under the bubble: {title, url, price_from, currency, image} — title required, everything else optional). Honors Idempotency-Key. 409 when closed.
/conversations/{id}/typing/{"name": "OrderBot", "on": true} , caduca automáticamente tras unos 8 s.
/conversations/{id}/assign/{"user_id": 12} (or null to unassign) — teammates come from /members/.
/conversations/{id}/handoff/Marcar que se necesita una persona; aparece en la bandeja como una transferencia de la IA, incluidas las reglas de enrutamiento.
/conversations/{id}/{"status": "closed" | "open"}. Closing fires conversation.closed.
/widgets/{widget_id}/visitors/{session}/message/Proactive push: reuses the session's live conversation or opens one; body + buttons + blocks + products. Honors Idempotency-Key.
Integraciones, su propio software como integración de primera clase
/integrations/ · POSTList, or register one: slug, name, base_url (https), optional description/icon_url/homepage_url, auth_type (none · bearer · api_key · basic) + credential, and actions inline. Returns the signing_secret you verify our calls with.
/integrations/{slug}/ · PATCH · DELETEPATCH takes the same fields; actions upserts by name and replace_actions: true makes your list authoritative (deploy-from-source). rotate_signing_secret rolls the secret; active: false switches it off everywhere at once.
/integrations/{slug}/actions/ · POST · DELETE /{name}/Each action: name (dotted, e.g. orders.lookup), path, method, risk, description (what the AI reads), parameters ({name, type, required, description, enum}), ai_enabled. GET also returns the reserved-action catalog.
/integrations/{slug}/install/ · /uninstall/widget_ids (all visible widgets when omitted), actions allowlist, enabled.
/integrations/{slug}/actions/{name}/run/Invoke it now through the real call path. arguments, optional conversation_id for visitor context. Returns {status, result, blocks}.
Contenido multimedia
/media/Multipart file (≤10 MB) → {media: {url, absolute_url, filename, content_type, size, is_image}}. Put url in an image/video/file block.
Tickets
/tickets/Filters: status (open · pending · resolved), email; paginated.
/tickets/email, message, optional name, category_id, priority (low · normal · high · urgent), submission_id (UUID idempotency key). The requester gets the standard acknowledgement email.
/tickets/{id}/ · PATCHDetail with the comment thread; PATCH status, priority, assignee_user_id.
/tickets/{id}/reply/body — emails the requester; internal: true for a private note.
/ticket-categories/Las categorías del espacio de trabajo para crear tickets.
Contactos, sincronice su base de datos de usuarios
/contacts/Upsert by email (201 created / 200 updated): profile fields + custom_fields (≤30 keys, merged).
/contacts/ · GET/PATCH/DELETE /contacts/{id}/Search with email (exact) or q; standard pagination.
Visitantes, identidad y personalización
/widgets/{widget_id}/visitors/{session}/identify/email (required), name, data (custom fields), verified + verify_hours (≤72, default 12) — see Identidad. Las sesiones pueden aprovisionarse de antemano.
/widgets/{widget_id}/visitors/{session}/unverify/Revocar la validación, llamar a al cerrar sesión.
/widgets/{widget_id}/visitors/{session}/Identidad actual: correo electrónico, nombre y estado de verificación.
Base de conocimiento
/articles/q full-text search, published=true filter — perfect for bot answer lookups.
/articles/ · GET/PATCH/DELETE /articles/{id}/Sincronice la documentación mediante código. El contenido HTML se limpia en el servidor con una lista de permitidos.
Configuración y metadatos
/widgets/{widget_id}/ · PATCHLeer o actualizar la configuración del widget, saludo, colores, botones de acción de Inicio, preguntas rápidas, horario de atención y los mismos campos validados que edita el personalizador del panel.
/members/Compañeros (user_id, name, role) para asignaciones.
/stats/Recuentos en directo: conversaciones abiertas/pendientes, tickets abiertos, contactos y artículos publicados.
/me/Su espacio de trabajo, ámbito de la credencial y widgets (ID + claves de inserción).
/schema/La descripción legible por máquinas de todo lo anterior: endpoints, eventos, tipos de bloques, acciones reservadas, códigos de error y límites. Genere su cliente a partir de ella.
Identidad: omitir la nueva verificación para usuarios conectados
El widget guarda su ID de sesión en el navegador. Léalo en el cliente, envíelo a su backend con su propia cookie de sesión y valide la identidad entre servidores:
# your backend, right after your own auth check
curl -X POST https://conecto.chat/api/v1/widgets/7/visitors/$CONECTO_SESSION/identify/ \
-u "ck_...:cs_..." -H "Content-Type: application/json" \
-d '{"email": "maya@acme.io", "name": "Maya",
"verified": true, "verify_hours": 24,
"data": {"plan": "pro", "customer_since": "2024"}}'While the vouch lasts, Conecto treats the email as verified: the widget knows their name, chats attach to the right CRM contact, and flows that normally demand an email OTP — refund lookups, order changes, sensitive account data through the AI's tools — proceed without one. Your attestation is as strong as our code-by-email, because you actually authenticated them. Only vouch sessions your backend has verified, and call /unverify/ on logout.
Webhooks
Manage in the dashboard or via GET/POST /webhooks/ y DELETE /webhooks/{id}/ (https only, ≤10 per workspace, optional per-widget scope). Deliveries carry X-Conecto-Event, X-Conecto-Delivery, X-Conecto-Timestamp and the HMAC signature, and time out after 2.5s — respond 200 fast, process async. Events:
conversation.createdprimer mensaje de visitante de un hilo nuevo, incluidos formularios de la pestaña Iniciomessage.createdcada mensaje de visitante, el activador del bot personalizadoconversation.closedcerrada por un agente o la APIconversation.assignedasignado a un compañero o sin asignarconversation.handoffmarcado como que necesita una personaconversation.ratedvaloración CSAT del visitante (1–5 + comentario)ticket.createdcualquier origen: formulario del widget, IA, bots, automatizaciones, APIticket.updatedcambió el estado, la prioridad o la persona asignadacontact.createduna nueva persona entró en el CRM, sincronícela con el suyovisitor.identifieduna sesión se identificó mediante la API, con su estado de validaciónLa entrega es al menos una vez y sin orden garantizado. Every payload carries an id (also in X-Conecto-Delivery): store it, skip ids you have already handled, and both replays and duplicates stop being your problem. When your bot answers a delivery, pass that same id as the Idempotency-Key and a redelivery can never make it speak twice.
app.post('/conecto', express.raw({ type: '*/*' }), async (req, res) => {
const sig = 'sha256=' + crypto.createHmac('sha256', WEBHOOK_SECRET)
.update(req.body).digest('hex')
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(req.get('X-Conecto-Signature') || '')))
return res.sendStatus(401)
const { id, event, data } = JSON.parse(req.body)
res.sendStatus(200) // ack first, work after
if (await seen(id)) return // at-least-once: dedupe on the delivery id
if (event === 'message.created' && data.message.sender === 'visitor') {
await fetch(`https://conecto.chat/api/v1/conversations/${data.conversation.id}/messages/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Idempotency-Key': id, ...auth },
body: JSON.stringify({ body: await answer(data) }),
})
}
})Everything here is designed to sit under an SDK: stable envelopes, slug-addressed integrations, typed errors, cursor pagination, idempotent writes, one signature scheme in both directions, and GET /schema/ to generate from. Building something? Habla con nosotros , queremos que los desarrolladores creen sobre Conecto y damos prioridad a sus peticiones.