Udviklere / Python

Den Python-SDK

Læs og besvar chats, send billeder og video, byg bots, og lad AI-agenten slå oplysninger op i jeres egne systemer med Python. Alt, hvad REST API'et gør, med de vanskelige dele håndteret: kontrol af, at en anmodning virkelig kom fra os, undgåelse af dobbelte svar på samme hændelse, sikre nye forsøg og registrering af ugyldige beskeder, før de forlader processen.

Ny på Conecto? Start her , fem minutters begreber, hvorefter resten af siden giver sig selv.

pip install conectov0.1.0Python 3.9+MIT

One dependency (requests) · Type hints throughout · På PyPI

Start her

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

Hvis I aldrig har brugt Conecto før, indeholder dette afsnit hele tankemodellen. Fem minutter her gør resten af siden selvforklarende.

Hvad Conecto er

En chatwidget på jeres website. Besøgende skriver i den; jeres team, en AI-agent eller jeres egen kode svarer. Alt nedenfor er en måde at føre jeres kode ind i det kredsløb.

De fem navneord

Næsten alle metoder i dette SDK modtager eller returnerer et af disse objekter.

Arbejdsområde

Jeres konto. Legitimationsoplysningen tilhører præcis én konto og kan aldrig se en anden.

Widget

One installed chat box. You might have one per website or brand. Has an id.

Besøgende

A browser on your site, identified by a session string stored in that browser.

Samtale

One thread between a visitor and you. Has an id, and a status of open or closed.

Besked

One bubble in a thread. Sent by a visitor, an agent (human), a bot, or the system.

De tre ting, I kan bygge

1. Et script

Køres efter jeres tidsplan. Synkroniserer kontakter, åbner sager og sender kampagnebeskeder. Intet skal hostes; scriptet kalder blot API'et.

2. En bot

En af jeres webservere, som vi underretter, når en besøgende skriver. I bestemmer svaret. I styre samtalen.

3. Et plugin

En af jeres webservere, som vi kalder, når AI har brug for en oplysning, kun I har. AI'en styrer samtalen; I leverer opslagene.

Bot eller plugin? Hvis I vil skrive de ord, den besøgende læser, skal I bygge en bot. Hvis I hellere vil lade AI'en tale og blot give den adgang til jeres data, for eksempel ordrer, abonnementer og lagerbeholdning, skal I bygge et plugin. De fleste teams ender med at vælge et plugin.

Ord, der bruges på denne side

Jargon, defineret én gang. Intet her er specifikt for Conecto bortset fra de sidste to begreber.

WebhookNår noget sker, sender vi en HTTP-anmodning til en af jeres URL'er i stedet for, at I gentagne gange skal spørge os. I skal bruge en server, der kan nås fra internettet.
SignaturEn hash, som vi føjer til hver anmodning, beregnet med en hemmelighed, kun I og vi kender. Kontrollen beviser, at anmodningen virkelig kom fra os og ikke blev ændret. SDK'et gør dette for jer.
Mindst én gangHvis vi ikke kan afgøre, om I modtog noget, sender vi det igen. I kan derfor indimellem få den samme hændelse to gange, og jeres kode skal opdage det i stedet for at handle to gange.
IdempotentAt gøre det to gange giver samme resultat som at gøre det én gang. Hvis I sender en besked to gange med samme nøgle, returnerer det andet kald den første besked i stedet for at offentliggøre en dublet.
Markørbaseret pagineringLange lister kommer i sider. I stedet for »side 3« sender I det ID, I fik sidst. SDK'et skjuler dette; I skal blot iterere.
BlokEt rich content-element i en besked, for eksempel et billede, en video, et kort eller en række knapper. Se Rigt indhold.
Verificeret besøgendeEn person, hvis e-mailadresse vi har verificeret , ved at sende en kode via e-mail, eller fordi jeres website har fortalt os, at personen er logget ind. Alt, der berører en persons private data, venter på denne bekræftelse.

Hvilket afsnit har jeg brug for?

Jeg vil…Gå til
Læs eller besvar chats fra et scriptSamtaler
Send et billede, en GIF, en video eller et kortRigt indhold
Svare besøgende automatisk med min egen logikByg en bot
Lad AI'en slå oplysninger op i mine systemerByg et plugin
Forbind en butik, som AI'en kan søge iByg et plugin
Spring e-mailkoden over for brugere, der allerede er logget indIdentitet
Hold mit CRM-system synkroniseretKontakter og besøgende
Vide, hvad jeg skal fange, når noget går galtFejl og nye forsøg

Installer og godkend

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok
pip install conecto

Opret en legitimationsoplysning i Indstillinger → Udviklere. I får en klient-id (ck_…) and a hemmelighed (cs_…). The secret is shown once and stored hashed, so put it somewhere your code can read and your repository cannot.

export CONECTO_CLIENT_ID=ck_your_client_id
export CONECTO_SECRET=cs_your_secret
from conecto import Conecto

client = Conecto()                    # reads the environment
client = Conecto("ck_...", "cs_...")  # or pass them directly

print(client.ping()["workspace"]["name"])

Call ping() once at startup. It raises AuthenticationError immediately if the credential is wrong or revoked — much better than finding out mid-conversation. A credential can cover the workspace or be scoped to one widget; a scoped one simply cannot see other widgets' conversations.

Hurtig start

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

Tre ting, I kan gøre i løbet af de første fem minutter.

Læs, hvad der sker

for convo in client.conversations.list(status="open"):
    who = convo.visitor.email if convo.visitor else "anonymous"
    print(f"#{convo.id}  {who}  {convo.preview}")

Svare nogen

convo = client.conversations.get(4821)
print(convo.messages[-1].body)          # what they said last
convo.reply("On it — give me one moment.")

Send noget andet end tekst

from conecto import blocks

convo.reply("Here's how to reset it:", blocks=[
    blocks.image("https://cdn.you.com/reset.gif", alt="Reset flow"),
    blocks.buttons([
        blocks.reply_button("That worked"),
        blocks.link_button("Full guide", "https://docs.you.com/reset"),
    ]),
])

Sådan er hele biblioteket opbygget: en klient med ressourcer, objekter, der kan handle på sig selv, og builders til alt struktureret.

Klienten

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

Opret én, og behold den. Den genbruger forbindelser og kan deles sikkert mellem tråde.

client = Conecto(
    client_id="ck_...",
    secret="cs_...",
    base_url="https://conecto.chat/api/v1",  # override for staging
    timeout=30.0,        # seconds per request
    max_retries=2,       # timeouts, 429s and 5xx
    app="acme-billing",  # added to the User-Agent; shows up in our logs
)

Miljø

VariabelAngiver
CONECTO_CLIENT_IDKlient-ID'et.
CONECTO_SECRETHemmeligheden.
CONECTO_BASE_URLAPI'ets basis-URL. Behøves sjældent.

Ressourcer

AttributDækker
client.conversationsList, læs, svar, vis skrivning, overdrag, tildel og luk.
client.contactsCRM-systemet: opret eller opdater efter e-mailadresse, søg, opdater og slet.
client.visitorsSessioner, identitetsbekræftelse og proaktive beskeder.
client.ticketsOpret, opdater, svar og administrer kategorier.
client.articlesSøg efter og offentliggør indhold i hjælpecenteret.
client.integrationsRegistrér, installer, kør og deploy plugins.
client.widgetsLæs og opdater widgetkonfigurationen.
client.mediaUpload filer til brug i blokke.
client.webhooksAbonnér endpoints på hændelser.
client.membersTeammedlemmer, til tildeling.
client.metame(), schema(), stats().

Versionsafvigelse

Hvert svar indeholder serverens API-version. Log den ved opstart. Hvis versionen ændres, og et felt, I bruger, forsvinder, er dette det første, I vil vide.

print(client.api_version)   # what the server last reported
print(client.sdk_version)   # what this SDK was built against

limits = client.meta.schema()["limits"]
assert limits["blocks_per_message"] >= 4

client.meta.schema() returnerer hele API'et som JSON, herunder endpoints, hændelser, bloktyper, reserverede handlinger, fejlkoder og alle grænser. Det leveres af den samme kode, som håndhæver reglerne, og kan derfor ikke afvige fra virkeligheden.

Samtaler

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

A thread between a visitor and your workspace. Messages have a sender af visitor, agent, bot eller system.

Liste og læsning

page = client.conversations.list(status="open", limit=25)

page.items                     # this page only
for convo in page:             # every page, fetched lazily
    ...

convo = client.conversations.get(4821)
convo.status, convo.visitor.email, convo.messages[-1].body

# Poll cheaply: only what is newer than what you already have.
fresh = client.conversations.messages(4821, since_id=last_seen_id)

Svare

convo.reply("I can refund order #1284 — confirm?",
            buttons=["Yes, refund it", "Talk to a human"])

Et tryk på en hurtigsvarsknap kommer tilbage som en almindelig besked fra den besøgende med den tekst, så jeres handler behøver ikke et særtilfælde.

ArgumentGør
bodyTeksten, op til 4.000 tegn.
blocksRich content, se Rigt indhold.
buttonsOp til 6 hurtigsvar.
productsOp til 4 produktkort, samme antal som den indbyggede AI vedhæfter.
ask_emailViser den indbyggede formular til indsamling af e-mailadresser.
ticket_formVedhæfter den indbyggede formular til at åbne en sag.
internalEn note kun til agenter. Den besøgende ser den aldrig.
idempotency_keySend et leverings-ID for webhooken med, så et nyt forsøg ikke kan offentliggøre dobbelt.

Styring af tråden

convo.typing()                  # "…is typing", expires after ~8s
convo.reply("Working on it…")

convo.reply("Escalating: refund over policy limit.", internal=True)
convo.handoff()                 # flag for a human; routing rules apply
convo.assign(user_id=12)        # ids come from client.members.list()

convo.close()
convo.reopen()

Writing to a closed thread raises ConversationClosed. Reopen it first, or catch it — see Fejl.

Rigt indhold

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

Hver besked kan indeholde op til ti blokke: billeder og GIF'er, video, lyd, filer, indlejringer fra tredjeparter, kortkarruseller, etiket/værdi-lister, knapper og skillelinjer. De vises i widgetten og i agentens indbakke.

Blokke er almindelige dictionaries under overførslen, så I kunne skrive dem manuelt. Builders findes, fordi en forkert stavet nøgle kan få en kunde til at sige, at billedet aldrig blev vist. De anvender de samme regler som serveren, mens traceback stadig peger på jeres kode.

from conecto import blocks

convo.reply("Here's your order:", blocks=[
    blocks.list_block([
        blocks.row("Placed", "12 July 2026"),
        blocks.row("Status", "Shipped"),
        blocks.row("Carrier", "DHL", subtitle="Tracking 4Z8871",
                   url="https://track.dhl.com/4Z8871"),
    ], title="Order #1042"),
    blocks.divider(),
    blocks.cards([
        blocks.card("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=[blocks.link_button("Buy", "https://shop.you.com/cart")]),
    ]),
])

Alle bloktyper

BuilderNoter
blocks.image(url, alt, caption, link)Animated GIFs are images — point at the .gif. blocks.gif is an alias.
blocks.video(url, poster, autoplay, loop, muted)Direct files only (.mp4, .webm, .mov…).
blocks.embed(url)YouTube, Vimeo, Loom, Wistia og Spotify. Send det normale delingslink.
blocks.audio(url, title).mp3, .wav, .m4a
blocks.file(url, filename, size)En downloadrække.
blocks.cards([...], layout)carousel scrolls sideways, list stacks full width.
blocks.list_block([...], title)Label/value rows. blocks.rows is an alias.
blocks.buttons([...])Mix reply_button og link_button.
blocks.text(body)Et ekstra afsnit under andre blokke.
blocks.divider()En vandret linje.

To typer knapper

blocks.reply_button("Yes, refund it")                    # sends that text back
blocks.reply_button("Yes, refund it", "confirm_refund")  # nice label, stable value
blocks.link_button("Read the policy", "https://you.com/refunds")  # opens a tab

Giv en svarknap en udtrykkelig værdi, når etiketten skal være let at læse, men den tekst, som botten matcher, ikke skal ændres, hver gang nogen omskriver teksten.

Send en GIF

A .gif works. A muted, looping, autoplaying video is usually the better trade — a fraction of the bytes, and nobody can tell.

blocks.video("https://cdn.you.com/reset.mp4",
             autoplay=True, loop=True, muted=True)

Regler, som builders håndhæver

  • Every URL must be https. The widget runs on an https page, so anything else is blocked by the browser anyway.
  • Ti blokke pr. besked, ti kort, tolv listerækker og seks knapper.
  • Long strings are truncated, not rejected. Structural mistakes raise BlockError.
  • A YouTube link in a video block raises, and tells you to use embed.

Valideringen sker lokalt. reply(blocks=…) checks the list before sending, so a typo raises BlockError from your line rather than coming back as an HTTP 400 from inside the client.

Upload filer

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

Ingen offentlig URL til GIF'en, kvitteringen eller databladet? Upload filen, op til 10 MB, og brug resultatet.

media = client.media.upload("celebrate.gif")     # a path
media = client.media.upload(open("a.pdf", "rb")) # a file object
media = client.media.upload(raw_bytes, filename="receipt.png")

convo.reply("All set!", blocks=[blocks.image(media)])

A Media can be passed straight to blocks.image, blocks.video eller blocks.file.

Use media.url, not media.absolute_url. url er en sti, som widgetten opløser i forhold til sin egen oprindelse. Den samme besked fungerer derfor i udvikling, i en forhåndsvisning og i produktion, også den dag jeres domæne ændres.

Kontakter og besøgende

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

Kontakter

The CRM. upsert is keyed on email, so it is safe to point a nightly sync at.

client.contacts.upsert(
    "maya@acme.io",
    name="Maya Silva",
    company="Acme",
    custom_fields={"plan": "scale", "mrr": "480"},
)

contact = client.contacts.find("maya@acme.io")     # or None
for c in client.contacts.list(query="acme"):
    print(c.email, c.custom_fields.get("plan"))

custom_fields er sammenflettet, ikke erstattet. To jobs kan administrere deres egne nøgler uden at overskrive hinanden. Op til 30 nøgler pr. kald.

Besøgende

A browser session on a widget, keyed by (widget_id, session). The session id lives in the visitor's browser; read it client-side and send it to your backend.

visitor = client.visitors.get(widget_id=7, session=session_id)
visitor.email, visitor.verified

Proaktive beskeder

Send til en session uden at vente på et spørgsmål, for eksempel leveringsopdateringer, påmindelser om prøveperioder eller gendannelse af indkøbskurve.

client.visitors.message(
    widget_id=7, session=session_id,
    body="Still thinking it over? Your cart is saved:",
    blocks=[blocks.cards([
        blocks.card("Trail Runner 2", price="89.00 USD",
                    image="https://cdn.you.com/tr2.jpg",
                    url="https://shop.you.com/cart"),
    ])],
)

Den genbruger sessionens aktive samtale eller åbner en ny. En åben widget viser den ved næste polling; en lukket widget viser den næste gang, den åbnes, markeret som ulæst.

Sager og artikler

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

Sager

ticket = client.tickets.create(
    email="maya@acme.io",
    message="Card was declined on renewal.",
    priority="high",          # low · normal · high · urgent
)

client.tickets.update(ticket.id, status="pending", assignee_user_id=12)
ticket.reply("We've fixed the card on file — try again?")   # emails the requester
ticket.reply("Billing confirmed the retry.", internal=True)  # private note

Et offentligt svar genåbner en løst sag, fordi et svar på noget, der er markeret som afsluttet, næsten altid betyder en ny runde.

Hjælpecenterartikler

Nyttigt i begge retninger: søg derfra med en bot, før I åbner en sag, og send dokumentation ind fra det værktøj, hvor I skriver den.

hits = client.articles.list(query="refund", published=True)
if hits:
    convo.reply(f"This might help: “{hits[0].title}”")

client.articles.create("Shipping times", "<p>2-4 business days.</p>",
                       published=True)

HTML renses på serversiden mod en tilladelsesliste med sikre tags gennem samme proces, som editoren i dashboardet bruger. Scripts og typografi fjernes, før noget gemmes.

Byg en bot

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

A bot is a webhook and a reply. Bot handles the five things that are easy to get subtly wrong — signature verification, delivery deduplication, event routing, filtering out your own messages, and answering fast — so your code is the part that is yours.

from conecto import Conecto, Bot, blocks

client = Conecto()
bot = Bot(client, secret=WEBHOOK_SECRET)

@bot.on_message
def handle(ctx):
    text = ctx.text.lower()

    if "human" in text:
        ctx.reply("Of course — connecting you now.")
        ctx.handoff()
        return

    if "order" in text:
        if not ctx.verified:
            ctx.reply("What's the email on the order?", ask_email=True)
            return
        order = lookup_order(ctx.verified_email)     # your system
        ctx.reply("Here it is:", blocks=[
            blocks.list_block([
                blocks.row("Status", order["status"]),
                blocks.row("Carrier", order["carrier"]),
            ], title=order["number"]),
        ])
        return

    ctx.reply("I'm not sure about that one — let me get someone who is.")
    ctx.note(f"Bot could not classify: {ctx.text!r}")
    ctx.handoff()

Udbydning

# Flask
app.add_url_rule("/conecto", view_func=bot.flask_view(), methods=["POST"])

# FastAPI (handlers run in a worker thread, so they never stall the loop)
app.post("/conecto")(bot.fastapi_route())

# Django (urls.py)
path("conecto/", csrf_exempt(bot.django_view()))

# No framework at all
from wsgiref.simple_server import make_server
make_server("", 8000, bot.wsgi_app()).serve_forever()

Abonnér derefter endpointet:

hook = client.webhooks.create(
    "https://bots.you.com/conecto",
    ["message.created", "conversation.created", "conversation.rated"],
)
print(hook.secret)   # store this — it is how you verify deliveries

Handlers

DekoratørUdløses af
@bot.on_messageEn besked fra en besøgende. Beskeder fra bots og agenter når aldrig denne, hvilket forhindrer en bot i at svare sig selv.
@bot.on_conversation_startedconversation.created , hilse eller angive tilstand.
@bot.on_ratingconversation.rated. ctx.event.rating has score og comment.
@bot.on_ticket · @bot.on_contactticket.created · contact.created.
@bot.on("event.name")Any event by name. @bot.on("*") catches everything.
@bot.on_errorEn handler udløste en undtagelse. Uden en særskilt handler logges og ignoreres fejlene.

Hvad en handler modtager

På ctxEr
ctx.textTeksten i den besøgendes besked.
ctx.verified · ctx.verified_emailOm identiteten er verificeret, og hvilken adresse der er verificeret.
ctx.conversation_id · ctx.widget_id · ctx.sessionID'er, I kan handle på.
ctx.reply(...)Answer. Takes everything conversations.reply does.
ctx.note(...)En note kun til agenter.
ctx.typing() · ctx.handoff() · ctx.assign() · ctx.close()Styr tråden.
ctx.history()Hent hele beskedhistorikken. Én anmodning.
ctx.client · ctx.eventLow-level-klienten og den fortolkede hændelse, til alt andet.

Kører I mere end én worker? Deduplication is in-process by default, so two workers can each handle the same delivery once. Pass your own store — anything with add(id) -> bool, backed by Redis or your database.

class RedisSeen:
    def add(self, delivery_id):
        # True if new, False if we have handled it before
        return bool(redis.set(f"conecto:{delivery_id}", 1, nx=True, ex=86400))

bot = Bot(client, secret=WEBHOOK_SECRET, seen=RedisSeen())

Byg et plugin

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

En bot besvarer webhooks. En plugin går i den modsatte retning: I angiver, hvad jeres systemer kan gøre, og AI-agenten bestemmer, hvornår funktionerne skal bruges under en samtale. I skriver aldrig samtalelogikken, kun opslaget.

Sådan bliver en butik, et faktureringssystem eller et CRM-system, vi aldrig har hørt om, en fuldgyldig integration.

from conecto import Conecto, Plugin, parameter

plugin = Plugin("acme-store", base_url="https://api.acme.com/conecto")

@plugin.action(
    "catalog.search_products",
    description="Search the Acme catalog by keyword.",
    parameters=[parameter("query", required=True,
                          description="Search words from the visitor.")],
)
def search(req):
    hits = catalog.search(req.get("query"), limit=req.get("limit", 4))
    if not hits:
        return req.not_found()
    return req.ok(products=[
        {"title": p.name, "url": p.url, "image": p.image,
         "price_from": f"{p.price:.2f}", "currency": "USD",
         "available": p.stock > 0}
        for p in hits
    ])

@plugin.action(
    "orders.get_status",
    risk="verified_read",
    description="Status of one of the visitor's own orders.",
    parameters=[parameter("order_number", required=True)],
)
def order_status(req):
    order = orders.find(req.get("order_number"), email=req.verified_email)
    return req.ok(**order) if order else req.not_found()

Udbyd og registrér

# One route serves every action.
app.add_url_rule("/conecto/<path:action>", view_func=plugin.flask_view(),
                 methods=["POST"])

# Tell Conecto it exists. Idempotent — run it on every release.
integration = plugin.deploy(Conecto())
print(integration.signing_secret)   # set this in your environment

deploy() opretter eller opdaterer integrationen, så den svarer til jeres kode, sletter de handlinger, I har fjernet, og installerer den i jeres widgets. Intet eksponeres for AI'en, før integrationen er installeret.

De fem svar

return req.ok(status="Shipped", carrier="DHL")   # success
return req.ok(result, blocks=[...])              # …plus rich content in the reply
return req.not_found()                           # nothing matched — a normal outcome
return req.verify_required()                     # "I need a proven identity"
return req.error("That subscription was already cancelled.")

Write error messages for the visitor, not for your logs: the AI may relay them. Returning a plain dict is also fine — it is treated as the result.

Risikoniveauer

RisikoBetyder
public_readKatalog, tilgængelighed og dokumentation. Ingen identitet kræves.
verified_readÉn persons data. Afvises, indtil e-mailadressen er verificeret.
public_writeEn ændring, der ikke kræver identitet, for eksempel tilmelding til et nyhedsbrev eller indsamling af leads.
writeEn ændring af en persons konto. Verificeret, revideret og aldrig forsøgt igen i det skjulte.

req.verified_email er den eneste identitet, I kan stole på. An email in req.arguments is whatever the model parsed out of a chat. For verified_read og write the platform will not call you at all until we have proven who the visitor is, and the proven address is the one we hand you.

Reserverede handlinger

Nogle navne har en særlig betydning på platformen. Deklarer et af dem, så kobles jeres tjeneste til den funktion, der allerede findes for navnet.

NavnHvad I får
catalog.search_productsResults become product cards under the AI's replies, and can fill the widget's Home showcase. Return {"products": [{title, url, image, price_from, currency, available}]}.
orders.get_statusOrdrestatus, altid efter verificering.
orders.get_trackingSporing af forsendelser, altid verificeret.
orders.list_recentDen besøgendes seneste ordrer, altid verificerede.
account.lookupEn vilkårlig enkel post om den verificerede besøgende, for eksempel abonnement, kreditter eller fornyelsesdato.

Test det, før en besøgende gør det

client.integrations.run("acme-store", "catalog.search_products",
                        {"query": "trail shoes"})
# {'status': 'ok', 'result': {...}, 'blocks': []}

Dette kører den faktiske call path — the signature, your credential, the checks that stop your URL pointing anywhere private, the timeout, the parsing — so what you see is exactly what the AI will get. Pass conversation_id= to run in the context of a live chat, which is the only way a verified action can succeed.

Skriv en god beskrivelse

Den description is the highest-leverage string in your integration: it is what the AI reads to decide whether this action answers the question in front of it. Write it as an instruction to a capable colleague who cannot see your code.

Svag: »Slå ordre op.«
Stærk: »Slå status og leveringsdato op for en af den besøgendes egne ordrer ved hjælp af ordrenummeret. Brug dette, når personen spørger, hvor noget er, eller hvornår det kommer.«

Hvad platformen garanterer

  • 8 sekunder timeout, 128 kB svargrænse.
  • Calls are signed with your signing_secret — same scheme as webhooks.
  • Jeres URL opløses én gang og bindes til en offentlig IP-adresse; omdirigeringer afvises.
  • Alt, I returnerer, når modellen som data, aldrig som instruktioner, og nøgler, der ligner hemmeligheder, fjernes.
  • Tilpassede integrationer kræver et betalt abonnement med AI-agent. Op til 20 pr. arbejdsområde og 40 handlinger i hver integration.

Identitet

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

Alt, der berører én persons data, venter, indtil vi ved, hvem personen er. Det sker på en af to måder: med en kode via e-mail eller gennem jeres websites bekræftelse for en bruger, der allerede er logget ind.

# your backend, right after your own auth check
client.visitors.identify(
    widget_id=7,
    session=conecto_session,          # read from the visitor's browser
    email=user.email,
    name=user.name,
    verified=True,                    # the vouch
    verify_hours=24,                  # max 72, default 12
    data={"plan": user.plan, "customer_since": "2024"},
)

# when they log out
client.visitors.unverify(widget_id=7, session=conecto_session)

Så længe bekræftelsen gælder, behandler Conecto e-mailadressen som verificeret: widgetten kender navnet, chats knyttes til den rigtige kontakt, og flows, der ellers ville kræve en kode via e-mail, for eksempel ordreopslag, refunderinger, kontodata eller verificerede pluginhandlinger, fortsætter uden kode.

Stå kun inde for sessioner, som jeres backend faktisk har godkendt. Jeres bekræftelse behandles som lige så stærk som vores egen kode via e-mail, fordi I faktisk har godkendt personen. Den er tidsbegrænset og knyttet til sessionen, så en ny browser kræver en ny bekræftelse. Browseren kan aldrig selv markere sig som verificeret; kaldet er godkendt og hører hjemme på jeres server.

Webhooks og hændelser

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

Bot gør alt dette for jer. Brug low-level-hjælperne, når I vil verificere i kanten og behandle via en kø.

from conecto import webhooks

event = webhooks.parse(request.get_data(), request.headers, secret=SECRET)

event.id                  # the delivery id — dedupe on this
event.event               # "message.created"
event.conversation_id
event.text
event.is_visitor_message  # excludes your own bot's messages
event.visitor.verified

Verificér mod det ubehandlede indhold. Re-serializing JSON changes the bytes and the signature stops matching — this is the most common first-integration bug there is. Use request.get_data() in Flask, request.body in Django, await request.body() in FastAPI. Passing a parsed dict raises with this exact advice.

Hændelser

conversation.createdEn besøgende startede en samtale.
message.createdEn besøgende sendte en besked. Bot-triggeren.
conversation.closedEn samtale blev lukket.
conversation.assignedTildelt til eller fjernet fra et teammedlem.
conversation.handoffMarkeret som kræver hjælp fra et menneske.
conversation.ratedEn CSAT-vurdering kom ind (1 til 5 samt en kommentar).
ticket.createdEn sag blev åbnet, uanset kilde.
ticket.updatedEn sags status, prioritet eller tildelte person blev ændret.
contact.createdEn ny person blev føjet til CRM-systemet.
visitor.identifiedEn session blev identificeret via API'et.

Leveringssemantik

Leveringer sker mindst én gang og uden garanteret rækkefølge: if we cannot confirm you got one we send it again, and they will not always arrive in the order things happened. They also time out after 2.5 seconds — so answer 200 immediately and do the work afterwards.

  • Every payload carries an id (also in X-Conecto-Delivery). Store it and skip ids you have already handled.
  • Pass that same id as idempotency_key when you reply, and a redelivery can never make your bot speak twice.
  • X-Conecto-Timestamp lader jer afvise gamle leveringer; SDK'et kontrollerer dem som standard med fem minutters tolerance.
signature = webhooks.sign(SECRET, raw_body)      # what we send
webhooks.verify(raw_body, header_value, SECRET)  # True / False, never raises

Integrationskald, vi foretager til your plugin use the identical scheme with the integration's signing_secret — one verification routine covers both directions.

Fejl og nye forsøg

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

Every failure raises a subclass of ConectoError, so you can catch exactly what you expect.

from conecto import ConversationClosed, RateLimited, NotFoundError

try:
    convo.reply("Refunded — you'll see it in 3-5 days.")
except ConversationClosed:
    convo.reopen()
    convo.reply("Refunded — you'll see it in 3-5 days.")
except NotFoundError:
    log.warning("conversation vanished")
UndtagelseStatusBetyder
AuthenticationError401Manglende, ukendt eller tilbagekaldt legitimationsoplysning.
InvalidRequestError400Indholdet bestod ikke valideringen; beskeden forklarer hvorfor.
NotFoundError404Objektet findes ikke i dette arbejdsområde.
ConflictError409Denne identifikator er allerede i brug.
ConversationClosed409Reopen the thread first. A subclass of ConflictError.
LimitReached403En grænse for abonnementet eller arbejdsområdet.
PlanRequired403Abonnementet omfatter ikke denne funktion.
RateLimited429Over 300 requests/minute. Carries retry_after.
ServiceUnavailable503En afhængighed er ikke konfigureret.
ServerError5xxVores fejl. Sikkert at prøve igen.
BlockErrorUdløst lokalt af en blok-builder, før nogen anmodning.
SignatureError401Verificeringen af et webhook- eller integrationskald mislykkedes.
APIConnectionError · APITimeoutErrorAnmodningen fik aldrig noget svar.

Every exception carries code, message, status and the raw response.

Nye forsøg

Timeouts, rate limits (429) and server errors (5xx) are retried for you. Each retry waits a little longer than the last, with a random amount added so that a thousand clients recovering at once do not all come back in the same instant — and if we send a Retry-After, that wins.

Skriveoperationer forsøges også igen, hvilket normalt er farligt. Her er det sikkert, fordi hver skriveoperation har en idempotensnøgle, og et nyt forsøg genbruger samme nøgle. Serveren genkender det andet forsøg som det samme kald og gentager det ikke.

client = Conecto(max_retries=0)   # opt out entirely

Grænser, der er værd at kende

GrænseVærdi
Anmodninger pr. minut og legitimationsoplysning300
Beskedindhold4.000 tegn
Blokke pr. besked10
Hurtigsvarsknapper6
Upload medier10 MB
Integrationer pr. arbejdsområde20
Handlinger pr. integration40
Webhooks pr. arbejdsområde10

client.meta.schema()["limits"] indeholder altid de aktuelle tal.

Paginering

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

List endpoints return a Page you can use three ways.

page = client.contacts.list(query="acme")

page.items        # just this page — a list, so len() and [0] work
page.has_more     # is there another?

for contact in page:   # every page, fetched lazily as you consume
    ...

page.all()        # everything, collected into one list

Iterating is lazy: next(iter(page)) costs exactly one request no matter how many contacts exist. all() is fine for contacts and tickets; think twice on a busy workspace's conversations, where iterating and stopping early is usually what you meant.

Tester

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

The client takes a transport, which is the seam for tests. Anything with get, post, patch, delete og request works — no network, no credentials, no mocking library.

class FakeTransport:
    base_url = "https://conecto.test/api/v1"

    def __init__(self):
        self.calls = []
        self.responses = {}

    def request(self, method, path, **kw):
        self.calls.append((method, path, kw.get("json_body")))
        return self.responses.get(f"{method} {path}", {})

    def get(self, p, **kw): return self.request("GET", p, **kw)
    def post(self, p, json_body=None, **kw): return self.request("POST", p, json_body=json_body, **kw)
    def patch(self, p, json_body=None, **kw): return self.request("PATCH", p, json_body=json_body, **kw)
    def delete(self, p, **kw): return self.request("DELETE", p, **kw)


def test_bot_answers_refunds():
    transport = FakeTransport()
    client = Conecto(transport=transport)
    client.conversations.reply(1, "Refunded.")
    assert transport.calls[0][1] == "/conversations/1/messages/"

For bots og plugins bør I springe signaturverificeringen over i tests i stedet for at signere fixtures:

bot = Bot(client, verify=False)
plugin = Plugin("acme", base_url="https://x.test", verify_signatures=False)

Metodereference

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

Samtaler

client.conversations.list(status, widget_id, session, limit, before_id)

Samtaler, nyeste aktivitet først. Returnerer en Page.

client.conversations.get(id, since_id=None)

En samtale med sine beskeder. since_id returnerer kun det, der er nyere.

client.conversations.messages(id, since_id=None)

Kun beskederne.

client.conversations.reply(id, body, blocks, buttons, products, ask_email, ticket_form, internal, idempotency_key)

Send en botbesked. Findes også som send().

client.conversations.typing(id, on=True, name="Bot")

Vis eller ryd skriveindikatoren.

client.conversations.handoff(id)

Markér som kræver hjælp fra et menneske.

client.conversations.assign(id, user_id)

None fjerner tildelingen.

client.conversations.close(id) · reopen(id) · set_status(id, status)

Skift status.

Kontakter

client.contacts.list(email, query, limit, before_id) · get(id) · find(email)

find() returnerer én kontakt eller None.

client.contacts.upsert(email, name, title, company, location, phone, notes, custom_fields)

Opret eller opdater efter e-mailadresse. Findes også som create().

client.contacts.update(id, **fields) · delete(id)

Besøgende

client.visitors.get(widget_id, session)

Identitet og verificeringsstatus.

client.visitors.identify(widget_id, session, email, name, data, verified, verify_hours)

Tilknyt jeres bruger; verified=True står inde for identiteten.

client.visitors.unverify(widget_id, session)

Tilbagekald bekræftelsen. Kald ved logout.

client.visitors.message(widget_id, session, body, blocks, buttons, products)

En proaktiv besked.

Integrationer

client.integrations.list() · get(slug) · actions(slug)

actions() returnerer også kataloget over reserverede handlinger.

client.integrations.create(slug, base_url, name, description, auth_type, credential, actions)

Registrér én. Svaret indeholder signing_secret.

client.integrations.update(slug, actions, replace_actions, rotate_signing_secret, **fields)

replace_actions gør jeres liste autoritativ.

client.integrations.deploy(slug, base_url, actions, widget_ids, install=True)

Opret eller opdater, og installer derefter. Idempotent, så læg det i jeres deployscript.

client.integrations.install(slug, widget_ids, actions, enabled) · uninstall(slug, widget_ids)

actions er en tilladelsesliste; [] eksponerer intet.

client.integrations.run(slug, action, arguments, conversation_id)

Kald gennem den faktiske kaldsti.

client.integrations.delete(slug) · remove_action(slug, name)

Sager, artikler, widgets, medier, webhooks og metadata

client.tickets.list · get · create · update · reply · categories
client.articles.list · get · create · update · delete
client.widgets.get(id) · update(id, **fields)

Den fulde konfiguration ligger i widget.raw.

client.media.upload(file, filename, content_type)

Returnerer et Media-objekt, der kan sendes til en blok-builder.

client.webhooks.list() · create(url, events, widget_id) · delete(id)

Ukendte hændelsesnavne afvises lokalt.

client.members.list() · client.meta.me() · schema() · stats()

Modeller

Every returned object keeps the payload it was built from. Attribute access is the nice path; obj.raw is always there for a field this SDK version does not know about yet, so a release is never what stands between you and a new API field.

convo.status          # typed
convo.raw["status"]   # the same thing
convo["status"]       # shorthand for the raw payload
convo.to_dict()       # exactly what the API sent

Opskrifter

Sidder I fast? Spørg en AI om dette afsnit:ClaudeChatGPTGrok

Undgå en sag ved hjælp af jeres hjælpecenter

@bot.on_message
def deflect(ctx):
    hits = ctx.client.articles.list(query=ctx.text, published=True)
    if hits:
        ctx.reply(f"This might help: “{hits[0].title}”. Did that solve it?",
                  buttons=["Solved it", "Open a ticket"])
    else:
        ctx.reply("Let's get this to the team.", ticket_form=True)

Kvalificér et lead, og route det

@bot.on_message
def qualify(ctx):
    if not ctx.event.visitor or not ctx.event.visitor.email:
        ctx.reply("Happy to help — what's your work email?", ask_email=True)
        return

    ctx.client.contacts.upsert(
        ctx.event.visitor.email,
        custom_fields={"lead_source": "chat", "intent": classify(ctx.text)},
    )
    ctx.note("Qualified lead — routing to sales.")
    ctx.assign(SALES_USER_ID)
    ctx.handoff()

Red en dårlig vurdering

@bot.on_rating
def follow_up(ctx):
    rating = ctx.event.rating or {}
    if rating.get("score", 5) <= 2:
        ctx.client.tickets.create(
            email=ctx.event.visitor.email,
            message=f"Low CSAT ({rating['score']}/5): {rating.get('comment', '')}",
            priority="high",
        )

Natlig kontaktsynkronisering

for user in your_database.active_users():
    client.contacts.upsert(
        user.email, name=user.name, company=user.company,
        custom_fields={"plan": user.plan, "mrr": str(user.mrr)},
    )

En butik, vi aldrig har hørt om

@plugin.action("catalog.search_products",
               description="Search the catalog by keyword.",
               parameters=[parameter("query", required=True)])
def search(req):
    hits = your_catalog.search(req.get("query"))
    return req.ok(products=[to_conecto(p) for p in hits]) if hits else req.not_found()

Det er hele integrationen. Produktkort, Home-visningen og reglen, der forhindrer modellen i at indsætte rå URL'er, følger med det reserverede navn.

Bygger I noget? Tal med os , vi ønsker, at folk bygger med Conecto, og prioriterer det, udviklere efterspørger. SDK'et har MIT-licens og er open source; issues og pull requests er velkomne på GitHub.