Bouwen op Conecto
Een overzichtelijke REST API voor alles wat het platform doet: bouw bots die niet alleen antwoorden, maar ook kunnen handelen , in uw database, facturering en product, stuur afbeeldingen, GIF’s, video’s en kaarten naar de chat en uw eigen integraties bouwen die de AI-agent tijdens het gesprek aanroept, ongeacht de onderliggende software. Als de widget het kan, kan de API het aansturen.
Live in productie · Ondertekende webhooks · 300 verzoeken/min
De basis
Base URL https://conecto.chat/api/v1. Create a credential in Instellingen → Ontwikkelaars , u ontvangt een client-ID en een geheim (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"Werkt u met Python? De officiële SDK (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.
Snelheidslimiet 300 verzoeken/min per credential (429 + Retry-After beyond it). Errors are always {"error": {"code", "message"}}. List endpoints paginate with limit en 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.
Eén eindpunt beschrijft alle andere
GET /schema/ geeft het volledige oppervlak terug als JSON: elk eindpunt, elke gebeurtenis, elk bloktype, elke gereserveerde actie, elke foutcode en elke numerieke limiet. Dezelfde code die de limieten handhaaft levert dit antwoord, zodat het niet zoals een documentatiepagina van de actieve server kan afwijken. Genereer uw client hieruit of controleer bij het starten of de benodigde functie is uitgerold.
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.
Rich content: afbeeldingen, GIF’s, video en kaarten
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"}
]}
]
}'De bloktypen
imageurl, alt, caption, link — animated GIFs are just imagesvideourl to an .mp4/.webm/.mov, plus poster, autoplay, loop, mutedembedprovider youtube · vimeo · loom · wistia · spotify en de gewone deellink urlaudiourl naar een .mp3/.wav/.m4a-bestand, optioneel titlefileurl, filename, size — rendered as a download rowcardsitems[] of title · subtitle · text · image · url · price · badge · buttons; layout carousel or listlistrows[] van label · waarde · ondertitel · afbeelding · URL, voor besteloverzichten en verzendstappenbuttonsitems[]: {label, value} replies as the visitor, {label, url} opens a linktext · dividerextra alinea’s en een horizontale lijnURLs 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.
Een GIF versturen zonder eigen opslaglocatie
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.
Een kaartencarrousel
{"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"}]}
]
}]}Bouw uw eigen integratie
Shopify, Stripe en BigCommerce zijn integraties die wij hebben geschreven. Dit is degene die u schrijven. Beschrijf uw dienst met een basis-URL en een lijst acties, installeer deze op een widget en de AI-agent roept hem tijdens het gesprek aan. Achterliggende systemen kunnen hem niet van een ingebouwde integratie onderscheiden. Dat is precies de bedoeling: als uw winkel, facturering of CRM draait op een oplossing die wij niet kennen, hoeft u niet meer op onze ondersteuning te wachten.
1 · Registreren
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 · Op een widget installeren
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 · De aanroep beantwoorden
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-KeyAntwoord met een van vijf vormen:
{"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 relayEen gewoon JSON-object zonder een van deze sleutels wordt als het resultaat zelf behandeld. U kunt een actie dus richten op een eindpunt dat u al hebt. Limieten: 8 sec. time-out, 128 KB -antwoord. Alles wat u terugstuurt bereikt het model als gegevens in een JSON-envelope, nooit als instructies. Sleutels die op geheimen lijken, worden bij binnenkomst verwijderd.
4 · Testen voordat een bezoeker dat doet
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.
Risiconiveaus en de ene regel die u niet kunt uitschakelen
public_readcatalogus, beschikbaarheid, documentatie, geen identiteit vereistverified_readgegevens van één persoon. Geweigerd totdat het e-mailadres van de bezoeker is geverifieerdpublic_writeeen wijziging waarvoor geen identiteit nodig is, zoals nieuwsbriefinschrijving of leadverzamelingwriteeen wijziging in het account van één persoon. Geverifieerd en nooit stilzwijgend opnieuw geprobeerdVoor de laatste twee wordt het geverifieerde adres geleverd door ons, in visitor.verified_email — never taken from the model's arguments. Verification comes from an emailed code, or from your own site identiteit van een aangemelde gebruiker bevestigen. Geen enkele widgetinstelling kan een actie hiervan vrijstellen, want «van wie is deze bestelling?» is geen vraag die een schakelaar hoort te beantwoorden.
Gereserveerde acties: uw winkel, gekoppeld als een ingebouwde integratie
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 en 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.
Aangepaste integraties maken deel uit van de AI-agentabonnementen en zijn beperkt tot 20 per workspace met elk 40 acties. Uitgaande aanroepen gebruiken alleen HTTPS, worden vóór de verbinding omgezet en vastgezet op een openbaar IP-adres, en omleidingen worden geweigerd. Een integratie-URL kan dus nooit naar een privédoel verwijzen.
Snelstart: een eigen bot in drie stappen
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. Uw server abonneren (Instellingen → Ontwikkelaars → Webhooks, of via de 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. De gebeurtenis verifiëren en lezen. 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. Antwoorden met functies.
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 Rich content), {"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.
Botreceptenboek
Recepten die echt in productie worden gebruikt. Elk bestaat uit een webhookhandler en enkele API-aanroepen.
1 · De actiebot, wijzigt gegevens in uw software
Omdat de webhook terechtkomt op uw kan uw bot op uw server alles doen wat uw backend kan: naar uw database schrijven, uw factureringssysteem aanroepen of een record in uw boekhoudsoftware wijzigen. Gecombineerd met bevestigde identiteit u precies weet wie de vraag stelt. Daarom kan «boek deze bon van $ 250 onder Marketing» veilig worden uitgevoerd:
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"])
}Hetzelfde patroon werkt voor «voeg een plaats toe aan mijn abonnement», «wijzig de naam van mijn project» of «boek donderdag een afspraak». De bot is een dunne gesprekslaag boven uw eigen API. Gebruikt u liever de ingebouwde AI dan zelf een bot te schrijven? Dezelfde eindpunten als integratie registreren en de Conecto-AI roept ze tijdens het gesprek aan met dezelfde identiteitsverificatie, zonder dat u een toestandsmachine hoeft te onderhouden. Als u al MCP gebruikt, werkt een externe MCP-toolserver ook: Dashboard → AI-agenten → Integraties.
2 · Bestelstatusbot
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 voor ticketvermindering
Zoek eerst in uw helpcentrum; open alleen een ticket wanneer niets overeenkomt:
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 voor leadkwalificatie
// 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 · Proactieve lifecycleberichten
Stuur naar een bezoekerssessie zonder op een bericht te wachten, bijvoorbeeld verzendupdates, proefherinneringen of winkelwagenherstel:
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 · Een onbekende winkel, verbonden met de AI
Geen webhook en geen toestandsmachine: declareer de gereserveerde catalogusactie, verwijs naar uw zoekeindpunt en de AI beveelt artikelen uit uw catalogus aan met echte productkaarten.
// 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,
})) })
})Dat is de volledige integratie. De kaarten, de Home-selectie, de regel «opnieuw aanroepen voordat u een product noemt» en het verbod voor het model om ruwe URL’s te plakken, horen automatisch bij de gereserveerde naam.
7 · CSAT-opvolging
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")
}Veilig handelen op uw eigen systemen
Drie regels maken actiebots geschikt voor productie:
1. Eerst de identiteit. 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. Destructieve stappen bevestigen. Stuur de actie als vraag met knoppen («Bestelling #1284 terugbetalen?» · Ja / Nee), de tik komt terug als knoptekst, uw idempotente handler wordt uitgevoerd en het transcript legt de toestemming vast.
3. Bij twijfel overdragen aan een medewerker. 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.
Eindpuntreferentie
Gesprekken en berichten
/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 Rich content), 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} , verloopt automatisch na ongeveer 8 sec.
/conversations/{id}/assign/{"user_id": 12} (or null to unassign) — teammates come from /members/.
/conversations/{id}/handoff/Markeren dat een medewerker nodig is; verschijnt in de inbox als AI-overdracht, inclusief routeringsregels.
/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.
Integraties, uw eigen software als volwaardige integratie
/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}.
Media
/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/De categorieën van de workspace voor het maken van tickets.
Contacten, synchroniseer uw gebruikersdatabase
/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.
Bezoekers, identiteit en personalisatie
/widgets/{widget_id}/visitors/{session}/identify/email (required), name, data (custom fields), verified + verify_hours (≤72, default 12) — see Identiteit. Sessies kunnen vooraf worden ingericht.
/widgets/{widget_id}/visitors/{session}/unverify/Bevestiging intrekken, roep aan bij afmelden.
/widgets/{widget_id}/visitors/{session}/Huidige identiteit: e-mail, naam en verificatiestatus.
Kennisbank
/articles/q full-text search, published=true filter — perfect for bot answer lookups.
/articles/ · GET/PATCH/DELETE /articles/{id}/Synchroniseer documentatie via code. HTML-inhoud wordt aan serverzijde opgeschoond volgens een toelatingslijst.
Configuratie en metadata
/widgets/{widget_id}/ · PATCHLees of wijzig widgetconfiguratie, begroeting, kleuren, actieknoppen op Home, snelle vragen, openingstijden en dezelfde gevalideerde velden die de dashboardaanpasser bewerkt.
/members/Teamleden (user_id, name, role) voor toewijzing.
/stats/Live aantallen: open/in behandeling zijnde gesprekken, open tickets, contacten en gepubliceerde artikelen.
/me/Uw workspace, bereik van de inloggegevens en widgets (ID’s + insluitsleutels).
/schema/De machineleesbare beschrijving van alles hierboven: eindpunten, gebeurtenissen, bloktypen, gereserveerde acties, foutcodes en limieten. Genereer uw client hieruit.
Identiteit: nieuwe verificatie voor aangemelde gebruikers overslaan
De widget bewaart de sessie-ID in de browser. Lees deze aan de clientzijde, stuur hem met uw eigen sessiecookie naar uw backend en bevestig de identiteit vervolgens van server tot server:
# 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/ en 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.createdeerste bezoekersbericht in een nieuwe draad, inclusief formulieren op het tabblad Homemessage.createdelk bezoekersbericht, de trigger van de eigen botconversation.closeddoor een medewerker of de API geslotenconversation.assignedaan een teamlid toegewezen of niet langer toegewezenconversation.handoffgemarkeerd als medewerker nodigconversation.ratedCSAT-score van bezoeker (1–5 + opmerking)ticket.createdelke bron: widgetformulier, AI, bots, automatiseringen, APIticket.updatedstatus, prioriteit of toegewezen medewerker gewijzigdcontact.createdeen nieuw persoon kwam in het CRM, synchroniseer die met het uwevisitor.identifiedeen sessie is via de API geïdentificeerd, met de bevestigingsstatusDe bezorging is ten minste eenmaal en zonder gegarandeerde volgorde. 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? Praat met ons , we willen dat ontwikkelaars op Conecto bouwen en geven prioriteit aan hun verzoeken.