Utvecklare / Python

Den Python-SDK

Läs och besvara chattar, skicka bilder och video, bygg botar och låt AI-agenten slå upp information i era egna system med Python. Allt som REST API:t gör, med de besvärliga delarna redan hanterade: kontroll av att en begäran verkligen kom från oss, undvikande av dubbla svar på samma händelse, säkra nya försök och upptäckt av felaktiga meddelanden innan de lämnar processen.

Ny på Conecto? Börja här , fem minuters begrepp, sedan blir resten av sidan självklar.

pip install conectov0.1.0Python 3.9+MIT

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

Börja här

Har ni fastnat? Fråga en AI om det här avsnittet:ClaudeChatGPTGrok

Om ni aldrig har använt Conecto tidigare innehåller detta avsnitt hela tankemodellen. Fem minuter här gör resten av sidan självklar.

Vad Conecto är

En chattwidget på er webbplats. Besökare skriver i den; ert team, en AI-agent eller er egen kod svarar. Allt nedan är ett sätt att föra in er kod i den loopen.

De fem substantiven

Nästan alla metoder i denna SDK tar emot eller returnerar ett av dessa objekt.

Arbetsyta

Ert konto. Er autentiseringsuppgift tillhör exakt ett konto och kan aldrig se ett annat.

Widget

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

Besökare

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

Samtal

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

Meddelande

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

De tre saker ni kan bygga

1. Ett skript

Körs enligt ert schema. Synkroniserar kontakter, öppnar ärenden och skickar kampanjmeddelanden. Inget behöver driftas; skriptet anropar bara API:t.

2. En bot

En av era webbservrar som vi meddelar när en besökare skriver. Ni bestämmer svaret. Ni styra konversationen.

3. Ett plugin

En av era webbservrar som vi anropar när AI behöver en uppgift som bara ni har. AI:n styr konversationen; ni tillhandahåller uppslag.

Bot eller plugin? Om ni vill skriva orden som besökaren läser ska ni bygga en bot. Om ni hellre låter AI:n prata och bara ger den åtkomst till era data, till exempel beställningar, abonnemang och lagersaldon, ska ni bygga ett plugin. De flesta team väljer till slut ett plugin.

Ord som används på sidan

Jargong, definierad en gång. Ingenting här är specifikt för Conecto utom de två sista begreppen.

WebhookNär något händer skickar vi en HTTP-begäran till en av era URL:er i stället för att ni ska fråga oss upprepade gånger. Ni behöver en server som kan nås från internet.
SignaturEn hash som vi bifogar till varje begäran, beräknad med en hemlighet som bara ni och vi känner till. Kontrollen visar att begäran verkligen kom från oss och inte har ändrats. SDK:n gör detta åt er.
Minst en gångOm vi inte kan avgöra om ni tog emot något skickar vi det igen. Ni kan därför ibland få samma händelse två gånger, och er kod ska upptäcka det i stället för att agera två gånger.
IdempotentAtt göra det två gånger ger samma resultat som att göra det en gång. Om ni skickar ett meddelande två gånger med samma nyckel returnerar det andra anropet det första meddelandet i stället för att publicera en dubblett.
Markörbaserad pagineringLånga listor kommer i sidor. I stället för ”sida 3” skickar ni det ID som ni fick senast. SDK:n döljer detta; ni behöver bara iterera.
BlockEtt rich content-element i ett meddelande, till exempel en bild, video, ett kort eller en knapprad. Se Rikt innehåll.
Verifierad besökareNågon vars e-postadress vi har verifierad , genom att skicka en kod via e-post eller genom att er webbplats meddelar oss att personen är inloggad. Allt som berör en persons privata uppgifter väntar på denna bekräftelse.

Vilket avsnitt behöver jag?

Jag vill…Gå till
Läs eller besvara chattar från ett skriptSamtal
Skicka en bild, GIF-fil, video eller ett kortRikt innehåll
Svara besökare automatiskt med min egen logikBygg en bot
Låt AI:n slå upp information i mina systemBygg ett plugin
Anslut en butik som AI:n kan söka iBygg ett plugin
Hoppa över e-postkoden för användare som redan är inloggadeIdentitet
Håll mitt CRM-system synkroniseratKontakter och besökare
Veta vad jag ska fånga när något går felFel och nya försök

Installera och autentisera

Har ni fastnat? Fråga en AI om det här avsnittet:ClaudeChatGPTGrok
pip install conecto

Skapa en autentiseringsuppgift i Inställningar → Utvecklare. Ni får en klient-ID (ck_…) and a hemlighet (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.

Snabbstart

Har ni fastnat? Fråga en AI om det här avsnittet:ClaudeChatGPTGrok

Tre saker ni kan göra under de första fem minuterna.

Läs vad som händer

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

Svara någon

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

Skicka något som inte är text

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å är hela biblioteket uppbyggt: en klient med resurser, objekt som kan agera på sig själva och builders för allt strukturerat.

Klienten

Har ni fastnat? Fråga en AI om det här avsnittet:ClaudeChatGPTGrok

Skapa en och behåll den. Den återanvänder anslutningar och kan delas säkert mellan trådar.

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ö

VariabelAnger
CONECTO_CLIENT_IDKlient-ID:t.
CONECTO_SECRETHemligheten.
CONECTO_BASE_URLAPI:ts bas-URL. Behövs sällan.

Resurser

AttributOmfattar
client.conversationsLista, läsa, svara, visa skrivstatus, lämna över, tilldela och stänga.
client.contactsCRM-systemet: skapa eller uppdatera via e-postadress, sök, uppdatera och ta bort.
client.visitorsSessioner, identitetsintygande och proaktiva meddelanden.
client.ticketsSkapa, uppdatera, svara och hantera kategorier.
client.articlesSök och publicera innehåll i hjälpcentret.
client.integrationsRegistrera, installera, kör och driftsätt pluginer.
client.widgetsLäs och uppdatera widgetkonfigurationen.
client.mediaLadda upp filer för användning i block.
client.webhooksPrenumerera endpoints på händelser.
client.membersTeammedlemmar, för tilldelning.
client.metame(), schema(), stats().

Versionsavvikelse

Varje svar innehåller serverns API-version. Logga den vid start. Om versionen ändras och ett fält ni använder försvinner är detta det första ni vill känna till.

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() returnerar hela API:t som JSON, inklusive endpoints, händelser, blocktyper, reserverade åtgärder, felkoder och alla gränser. Det levereras av samma kod som tillämpar reglerna och kan därför inte glida från verkligheten.

Samtal

Har ni fastnat? Fråga en AI om det här avsnittet:ClaudeChatGPTGrok

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

Lista och läsa

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)

Svara

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

Ett tryck på en snabbsvarsknapp återkommer som ett vanligt besökarmeddelande med den texten, så er handler behöver inget specialfall.

ArgumentGör
bodyTexten, upp till 4 000 tecken.
blocksRich content, se Rikt innehåll.
buttonsUpp till 6 snabbsvar.
productsUpp till 4 produktkort, samma antal som den inbyggda AI:n bifogar.
ask_emailVisar det inbyggda formuläret för insamling av e-postadresser.
ticket_formBifogar det inbyggda formuläret för att öppna ett ärende.
internalEn anteckning enbart för handläggare. Besökaren ser den aldrig.
idempotency_keySkicka med ett leverans-ID för webhooken så att ett nytt försök inte kan publicera dubbelt.

Styrning av 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 Fel.

Rikt innehåll

Har ni fastnat? Fråga en AI om det här avsnittet:ClaudeChatGPTGrok

Varje meddelande kan innehålla upp till tio block: bilder och GIF-filer, video, ljud, filer, inbäddningar från tredje part, kortkaruseller, etikett/värde-listor, knappar och avdelare. De visas i widgeten och i handläggarens inkorg.

Block är vanliga dictionaries vid överföringen, så ni skulle kunna skriva dem för hand. Builders finns eftersom en felstavad nyckel kan leda till att en kund säger att bilden aldrig visades. De tillämpar samma regler som servern medan traceback fortfarande pekar på er kod.

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")]),
    ]),
])

Alla blocktyper

BuilderAnteckningar
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 och Spotify. Skicka den vanliga delningslänken.
blocks.audio(url, title).mp3, .wav, .m4a
blocks.file(url, filename, size)En nedladdningsrad.
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 och link_button.
blocks.text(body)Ett extra stycke under andra block.
blocks.divider()En horisontell linje.

Två sorters knappar

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

Ge en svarsknapp ett uttryckligt värde när etiketten ska vara lättläst men texten som boten matchar inte ska ändras varje gång någon skriver om texten.

Skicka en GIF-fil

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 tillämpar

  • Every URL must be https. The widget runs on an https page, so anything else is blocked by the browser anyway.
  • Tio block per meddelande, tio kort, tolv listrader och sex knappar.
  • 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.

Ladda upp filer

Har ni fastnat? Fråga en AI om det här avsnittet:ClaudeChatGPTGrok

Saknas en publik URL för GIF-filen, kvittot eller databladet? Ladda upp filen, upp till 10 MB, och använd 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 är en sökväg som widgeten löser relativt sitt eget ursprung. Samma meddelande fungerar därför under utveckling, i en förhandsgranskning och i produktion, även den dag er domän ändras.

Kontakter och besökare

Har ni fastnat? Fråga en AI om det här avsnittet: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 är sammanfogade, inte ersatta. Två jobb kan hantera sina egna nycklar utan att skriva över varandra. Upp till 30 nycklar per anrop.

Besökare

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

Proaktiva meddelanden

Skicka till en session utan att vänta på en fråga, till exempel leveransuppdateringar, påminnelser om provperioder eller återställning av varukorgar.

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 återanvänder sessionens aktiva konversation eller öppnar en ny. En öppen widget visar den vid nästa pollning; en stängd widget visar den nästa gång den öppnas, markerad som oläst.

Ärenden och artiklar

Har ni fastnat? Fråga en AI om det här avsnittet:ClaudeChatGPTGrok

Ärenden

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

Ett offentligt svar öppnar ett löst ärende igen, eftersom ett svar på något som markerats som klart nästan alltid innebär en ny omgång.

Hjälpcenterartiklar

Användbart i båda riktningarna: sök därifrån med en bot innan ni öppnar ett ärende och skicka in dokumentation från det verktyg där ni 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 rensas på serversidan mot en tillåtelselista med säkra taggar, genom samma process som redigeraren i dashboarden använder. Skript och stilar tas bort innan något lagras.

Bygg en bot

Har ni fastnat? Fråga en AI om det här avsnittet: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()

Tillhandahålla

# 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()

Prenumerera sedan endpointen:

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

DekoratorUtlöses av
@bot.on_messageEtt meddelande från en besökare. Meddelanden från botar och handläggare når aldrig denna, vilket hindrar en bot från att svara sig själv.
@bot.on_conversation_startedconversation.created , hälsa eller ange tillstånd.
@bot.on_ratingconversation.rated. ctx.event.rating has score och 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 utlöste ett undantag. Utan en egen handler loggas och ignoreras felen.

Vad en handler får

På ctxÄr
ctx.textTexten i besökarens meddelande.
ctx.verified · ctx.verified_emailOm identiteten har verifierats och vilken adress som är verifierad.
ctx.conversation_id · ctx.widget_id · ctx.sessionID:n som ni kan agera på.
ctx.reply(...)Answer. Takes everything conversations.reply does.
ctx.note(...)En anteckning enbart för handläggare.
ctx.typing() · ctx.handoff() · ctx.assign() · ctx.close()Styr tråden.
ctx.history()Hämta hela meddelandehistoriken. En begäran.
ctx.client · ctx.eventLow-level-klienten och den tolkade händelsen, för allt annat.

Kör ni fler än en 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())

Bygg ett plugin

Har ni fastnat? Fråga en AI om det här avsnittet:ClaudeChatGPTGrok

En bot besvarar webhooks. En plugin går åt andra hållet: ni anger vad era system kan göra och AI-agenten bestämmer när funktionerna ska användas under ett samtal. Ni skriver aldrig konversationslogiken, bara uppslaget.

Så blir en butik, ett faktureringssystem eller ett CRM-system som vi aldrig har hört talas om en förstklassig 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()

Tillhandahåll och registrera

# 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() skapar eller uppdaterar integrationen så att den motsvarar er kod, tar bort åtgärder som ni har tagit bort och installerar den i era widgetar. Ingenting exponeras för AI:n innan integrationen har installerats.

De fem svaren

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.

Risknivåer

RiskBetyder
public_readKatalog, tillgänglighet och dokumentation. Ingen identitet krävs.
verified_readEn persons uppgifter. Avvisas tills e-postadressen har verifierats.
public_writeEn ändring som inte kräver identitet, till exempel registrering för nyhetsbrev eller insamling av leads.
writeEn ändring av en persons konto. Verifierad, granskad och aldrig tyst omprövad.

req.verified_email är den enda identitet ni kan lita på. An email in req.arguments is whatever the model parsed out of a chat. For verified_read och 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.

Reserverade åtgärder

Några namn har en särskild betydelse för plattformen. Deklarera ett av dem så kopplas er tjänst till den funktion som redan finns för namnet.

NamnVad ni 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_statusBeställningsstatus, alltid efter verifiering.
orders.get_trackingLeveransspårning, alltid verifierad.
orders.list_recentBesökarens senaste beställningar, alltid verifierade.
account.lookupValfri enkel post om den verifierade besökaren, till exempel abonnemang, saldo eller förnyelsedatum.

Testa innan en besökare gör det

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

Detta kör den verkliga 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 bra beskrivning

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å upp beställning.”
Stark: ”Slå upp status och leveransdatum för en av besökarens egna beställningar med hjälp av ordernumret. Använd detta när personen frågar var något är eller när det kommer.”

Vad plattformen garanterar

  • 8 sekunder timeout, 128 kB svarsgräns.
  • Calls are signed with your signing_secret — same scheme as webhooks.
  • Er URL löses en gång och binds till en publik IP-adress; omdirigeringar avvisas.
  • Allt ni returnerar når modellen som data, aldrig som instruktioner, och nycklar som ser ut att innehålla hemligheter tas bort.
  • Anpassade integrationer kräver ett betalt abonnemang med AI-agent. Upp till 20 per arbetsyta och 40 åtgärder i varje integration.

Identitet

Har ni fastnat? Fråga en AI om det här avsnittet:ClaudeChatGPTGrok

Allt som berör en persons uppgifter väntar tills vi vet vem personen är. Det sker på ett av två sätt: med en kod via e-post eller genom er webbplats intygande för en användare som redan är inloggad.

# 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 intygandet gäller behandlar Conecto e-postadressen som verifierad: widgeten känner till namnet, chattar kopplas till rätt kontakt och flöden som annars skulle kräva en kod via e-post, som beställningsuppslag, återbetalningar, kontouppgifter eller verifierade pluginåtgärder, fortsätter utan kod.

Intyga bara sessioner som er backend faktiskt har autentiserat. Ert intygande behandlas som lika starkt som vår egen kod via e-post, eftersom ni verkligen har autentiserat personen. Det är tidsbegränsat och knutet till sessionen, så en ny webbläsare behöver ett nytt intygande. Webbläsaren kan aldrig själv markera sig som verifierad; anropet är autentiserat och hör hemma på er server.

Webhooks och händelser

Har ni fastnat? Fråga en AI om det här avsnittet:ClaudeChatGPTGrok

Bot gör allt detta åt er. Använd low-level-hjälparna när ni vill verifiera vid gränsen och behandla 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

Verifiera mot det obehandlade innehållet. 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ökare startade en konversation.
message.createdEn besökare skickade ett meddelande. Botens trigger.
conversation.closedEn konversation stängdes.
conversation.assignedTilldelad till eller borttagen från en teammedlem.
conversation.handoffMarkerad som att mänsklig hjälp behövs.
conversation.ratedEtt CSAT-betyg kom in (1 till 5 samt en kommentar).
ticket.createdEtt ärende öppnades, oavsett källa.
ticket.updatedStatus, prioritet eller tilldelad person för ett ärende ändrades.
contact.createdEn ny person lades till i CRM-systemet.
visitor.identifiedEn session identifierades via API:t.

Leveranssemantik

Leveranser sker minst en gång och utan garanterad ordning: 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 gör att ni kan avvisa gamla leveranser; SDK:n kontrollerar dem som standard med fem minuters tolerans.
signature = webhooks.sign(SECRET, raw_body)      # what we send
webhooks.verify(raw_body, header_value, SECRET)  # True / False, never raises

Integrationsanrop som vi gör till your plugin use the identical scheme with the integration's signing_secret — one verification routine covers both directions.

Fel och nya försök

Har ni fastnat? Fråga en AI om det här avsnittet: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")
UndantagStatusBetyder
AuthenticationError401Saknad, okänd eller återkallad autentiseringsuppgift.
InvalidRequestError400Innehållet klarade inte valideringen; meddelandet anger varför.
NotFoundError404Objektet finns inte i denna arbetsyta.
ConflictError409Den identifieraren används redan.
ConversationClosed409Reopen the thread first. A subclass of ConflictError.
LimitReached403En gräns för abonnemanget eller arbetsytan.
PlanRequired403Abonnemanget omfattar inte denna funktion.
RateLimited429Over 300 requests/minute. Carries retry_after.
ServiceUnavailable503Ett beroende är inte konfigurerat.
ServerError5xxVårt fel. Säkert att försöka igen.
BlockErrorUtlöst lokalt av en block-builder, före någon begäran.
SignatureError401Verifieringen av ett webhook- eller integrationsanrop misslyckades.
APIConnectionError · APITimeoutErrorBegäran fick aldrig något svar.

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

Nya försök

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.

Skrivningar försöks också igen, vilket normalt är farligt. Här är det säkert eftersom varje skrivning har en idempotensnyckel och ett nytt försök återanvänder samma nyckel. Servern känner igen det andra försöket som samma anrop och upprepar det inte.

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

Gränser som är bra att känna till

GränsVärde
Begäranden per minut och autentiseringsuppgift300
Meddelandeinnehåll4 000 tecken
Block per meddelande10
Snabbsvarsknappar6
Ladda upp media10 MB
Integrationer per arbetsyta20
Åtgärder per integration40
Webhooks per arbetsyta10

client.meta.schema()["limits"] innehåller alltid de aktuella siffrorna.

Paginering

Har ni fastnat? Fråga en AI om det här avsnittet: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.

Testar

Har ni fastnat? Fråga en AI om det här avsnittet:ClaudeChatGPTGrok

The client takes a transport, which is the seam for tests. Anything with get, post, patch, delete och 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/"

För botar och pluginer ska ni hoppa över signaturverifieringen i tester i stället för att signera fixtures:

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

Metodreferens

Har ni fastnat? Fråga en AI om det här avsnittet:ClaudeChatGPTGrok

Samtal

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

Konversationer, senaste aktivitet först. Returnerar en Page.

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

En konversation med sina meddelanden. since_id returnerar bara det som är nyare.

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

Endast meddelandena.

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

Skicka ett botmeddelande. Finns även som send().

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

Visa eller rensa skrivindikatorn.

client.conversations.handoff(id)

Markera som att mänsklig hjälp behövs.

client.conversations.assign(id, user_id)

None tar bort tilldelningen.

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

Ändra status.

Kontakter

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

find() returnerar en kontakt eller None.

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

Skapa eller uppdatera via e-postadress. Finns även som create().

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

Besökare

client.visitors.get(widget_id, session)

Identitet och verifieringsstatus.

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

Koppla er användare; verified=True intygar identiteten.

client.visitors.unverify(widget_id, session)

Återkalla intygandet. Anropa vid utloggning.

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

Ett proaktivt meddelande.

Integrationer

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

actions() returnerar även katalogen över reserverade åtgärder.

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

Registrera en. Svaret innehåller signing_secret.

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

replace_actions gör er lista styrande.

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

Skapa eller uppdatera och installera sedan. Idempotent, så lägg det i ert deployskript.

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

actions är en tillåtelselista; [] exponerar ingenting.

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

Anropa via den verkliga anropsvägen.

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

Ärenden, artiklar, widgetar, media, webhooks och metadata

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

Den fullständiga konfigurationen finns i widget.raw.

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

Returnerar ett Media-objekt som kan skickas till en block-builder.

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

Okända händelsenamn avvisas 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

Recept

Har ni fastnat? Fråga en AI om det här avsnittet:ClaudeChatGPTGrok

Undvik ett ärende med hjälp av ert hjälpcenter

@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)

Kvalificera ett lead och dirigera 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()

Rädda ett dåligt betyg

@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",
        )

Nattlig 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 talas 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 är hela integrationen. Produktkort, Home-visningen och regeln som hindrar modellen från att klistra in obehandlade URL:er följer med det reserverade namnet.

Bygger ni något? Prata med oss , vi vill att människor ska bygga med Conecto och prioriterar det som utvecklare efterfrågar. SDK:n har MIT-licens och är open source; issues och pull requests är välkomna på GitHub.