Criar com Conecto
Uma API REST clara para tudo o que a plataforma faz: crie bots que não se limitam a responder, mas que podem agir , na sua base de dados, faturação e produto, envie imagens, GIF, vídeos e cartões para o chat e criar as suas próprias integrações que o agente de IA chama durante a conversa, seja qual for o software subjacente. Se o widget o consegue fazer, a API consegue controlá-lo.
Em produção · Webhooks assinados · 300 pedidos/min
Conceitos básicos
Base URL https://conecto.chat/api/v1. Create a credential in Definições → Programadores , recebe um ID de cliente e um segredo (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"Utiliza Python? O 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.
Limite de frequência 300 pedidos/min per credential (429 + Retry-After beyond it). Errors are always {"error": {"code", "message"}}. List endpoints paginate with limit e 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.
Um endpoint descreve todos os restantes
GET /schema/ devolve toda a superfície como JSON: cada endpoint, evento, tipo de bloco, ação reservada, código de erro e limite numérico. É servido pelo mesmo código que aplica esses limites, pelo que não pode divergir do servidor em execução como uma página de documentação. Gere o seu cliente a partir daí ou verifique no arranque se a funcionalidade necessária está implementada.
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.
Conteúdo enriquecido: imagens, GIF, vídeo e cartões
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 blocos
imageurl, alt, caption, link — animated GIFs are just imagesvideourl to an .mp4/.webm/.mov, plus poster, autoplay, loop, mutedembedprovider youtube · vimeo · loom · wistia · spotify e a ligação normal de partilha urlaudiourl para um ficheiro .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 · imagem · URL, para resumos de encomendas e etapas de enviobuttonsitems[]: {label, value} replies as the visitor, {label, url} opens a linktext · dividerparágrafos adicionais e uma linha 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 um GIF sem local para o 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.
Um carrossel de cartões
{"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"}]}
]
}]}Crie a sua própria integração
Shopify, Stripe e BigCommerce são integrações que nós criámos. Esta é a que você criar. Descreva o seu serviço com um URL base e uma lista de ações, instale-o num widget e o agente de IA chama-o durante a conversa. Os sistemas posteriores não conseguem distingui-lo de uma integração nativa. Esse é precisamente o objetivo: se a sua loja, faturação ou CRM funciona numa solução que desconhecemos, já não precisa de esperar pelo nosso suporte.
1 · Registá-la
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 · Instalá-la num 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 à chamada
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 com uma de cinco estruturas:
{"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 relayUm objeto JSON simples sem nenhuma dessas chaves é tratado como o próprio resultado. Assim, pode apontar uma ação para um endpoint que já possui. Limites: 8 s timeout, 128 KB de resposta. Tudo o que devolve chega ao modelo como dados dentro de um envelope JSON, nunca como instruções. As chaves que parecem segredos são removidas à entrada.
4 · Testá-lo antes de um 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.
Níveis de risco e a única regra que não pode desativar
public_readcatálogo, disponibilidade, documentação, não requer identidadeverified_readdados de uma pessoa. Recusado até que o e-mail do visitante seja verificadopublic_writeuma alteração que não requer identidade, como subscrição de newsletter ou recolha de leadswriteuma alteração na conta de uma pessoa. Verificada e nunca repetida silenciosamentePara os dois últimos, o endereço verificado é fornecido por nós, in visitor.verified_email — never taken from the model's arguments. Verification comes from an emailed code, or from your own site validação de um utilizador com sessão iniciada. Nenhuma definição do widget pode isentar uma ação desta regra, porque «de quem é esta encomenda?» não é uma pergunta que um interruptor deva responder.
Ações reservadas: a sua loja, ligada como uma integração 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 e 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.
As integrações personalizadas fazem parte dos planos Agente de IA e estão limitadas a 20 por espaço de trabalho, com 40 ações cada. As chamadas de saída utilizam apenas HTTPS, são resolvidas e fixadas a um IP público antes da ligação, e os redirecionamentos são recusados. Assim, um URL de integração nunca pode apontar para um destino privado.
Início rápido: um bot personalizado em três passos
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. Subscrever o seu servidor (Definições → Programadores → Webhooks, ou através da 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 e ler o 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 com funcionalidades.
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 Conteúdo 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.
Guia prático de bots
Receitas que são realmente utilizadas em produção. Cada uma é um handler de webhook com algumas chamadas à API.
1 · O bot de ações, altera elementos em seu software
Como o webhook chega a seu pode fazer no seu servidor tudo o que o backend consegue: escrever na sua base de dados, chamar o sistema de faturação ou atualizar um registo no software de contabilidade. Combinado com identidade validada sabe exatamente quem está a perguntar. Por isso, «arquivar este recibo de 250 $ em Marketing» pode ser executado em segurança:
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"])
}O mesmo padrão serve para «adicionar um lugar ao meu plano», «mudar o nome do meu projeto» ou «marcar uma hora na quinta-feira». O bot é uma fina camada de conversa sobre a sua própria API. Prefere utilizar a IA integrada em vez de criar um bot? Registar os mesmos endpoints como integração e a IA da Conecto chama-as durante a conversa com a mesma verificação de identidade, sem uma máquina de estados para manter. Se já utiliza MCP, também funciona um servidor remoto de ferramentas MCP: Painel → Agentes de IA → Integrações.
2 · Bot de estado de encomendas
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 redução de tickets
Pesquisar primeiro no centro de ajuda; abrir um ticket apenas se nada corresponder:
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 qualificação 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 · Mensagens proativas do ciclo de vida
Enviar para uma sessão de visitante sem esperar que escreva, por exemplo atualizações de envio, lembretes de avaliação ou recuperação do carrinho:
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 · Uma loja desconhecida, ligada à IA
Sem webhook nem máquina de estados: declare a ação reservada de catálogo, aponte-a para o seu endpoint de pesquisa e a IA recomenda artigos do catálogo com verdadeiros cartões de produto.
// 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,
})) })
})Esta é toda a integração. Os cartões, a seleção Início, a disciplina de «voltar a chamar antes de mencionar um produto» e a regra que impede o modelo de colar URL em bruto vêm incluídas com o nome reservado.
7 · Acompanhamento 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")
}Agir com segurança nos seus próprios sistemas
Três regras tornam os bots de ações prontos para produção:
1. Primeiro, a identidade. 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 os passos destrutivos. Enviar a ação como pergunta com botões («Reembolsar a encomenda n.º 1284?» · Sim / Não), o toque regressa como texto do botão, o seu handler idempotente é executado e a transcrição documenta o consentimento.
3. Transferir para uma pessoa em caso de dúvida. 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.
Referência de endpoints
Conversas e mensagens
/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 Conteúdo 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} , expira automaticamente após cerca de 8 s.
/conversations/{id}/assign/{"user_id": 12} (or null to unassign) — teammates come from /members/.
/conversations/{id}/handoff/Marcar que é necessária uma pessoa; aparece na caixa de entrada como uma transferência da IA, incluindo as regras de encaminhamento.
/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.
Integrações, o seu próprio software como integração de primeira classe
/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}.
Multimédia
/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/As categorias do espaço de trabalho para criar tickets.
Contactos, sincronize a sua base de dados de utilizadores
/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, identidade e personalização
/widgets/{widget_id}/visitors/{session}/identify/email (required), name, data (custom fields), verified + verify_hours (≤72, default 12) — see Identidade. As sessões podem ser pré-provisionadas.
/widgets/{widget_id}/visitors/{session}/unverify/Revogar a validação, chamar ao terminar sessão.
/widgets/{widget_id}/visitors/{session}/Identidade atual: e-mail, nome e estado de verificação.
Base de conhecimento
/articles/q full-text search, published=true filter — perfect for bot answer lookups.
/articles/ · GET/PATCH/DELETE /articles/{id}/Sincronize a documentação por código. O conteúdo HTML é limpo no servidor com uma lista de permissões.
Configuração e metadados
/widgets/{widget_id}/ · PATCHLer ou atualizar a configuração do widget, saudação, cores, botões de ação do Início, perguntas rápidas, horário de atendimento e os mesmos campos validados que o personalizador do painel edita.
/members/Membros da equipa (user_id, name, role) para atribuição.
/stats/Contagens em direto: conversas abertas/pendentes, tickets abertos, contactos e artigos publicados.
/me/O seu espaço de trabalho, âmbito da credencial e widgets (ID + chaves de incorporação).
/schema/A descrição legível por máquinas de tudo o que está acima: endpoints, eventos, tipos de blocos, ações reservadas, códigos de erro e limites. Gere o seu cliente a partir dela.
Identidade: ignorar nova verificação para utilizadores com sessão iniciada
O widget guarda o ID de sessão no navegador. Leia-o no cliente, envie-o para o backend com o seu próprio cookie de sessão e valide a identidade 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/ e 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.createdprimeira mensagem de visitante de uma nova conversa, incluindo formulários do separador Iníciomessage.createdcada mensagem de visitante, o acionador do bot personalizadoconversation.closedfechada por um agente ou pela APIconversation.assignedatribuído a um membro da equipa ou sem atribuiçãoconversation.handoffmarcado como necessitando de uma pessoaconversation.ratedavaliação CSAT do visitante (1–5 + comentário)ticket.createdqualquer origem: formulário do widget, IA, bots, automatizações, APIticket.updatedestado, prioridade ou responsável alteradocontact.createduma nova pessoa entrou no CRM, sincronize-a com o seuvisitor.identifieduma sessão foi identificada através da API, com o respetivo estado de validaçãoA entrega é pelo menos uma vez e sem ordem garantida. 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? Fale connosco , queremos que os programadores criem com a Conecto e damos prioridade ao que pedem.