Start free
Docs / User Guide / API & webhooks
User Guide

API & webhooks

Connections › API & webhooks in the left menu is where you connect SemAgent to your own systems: read conversations and customers and send replies with the API, and get notified instantly with webhooks when something happens.

The API & webhooks screen: keys and webhooks, with sample data The API & webhooks screen: keys and webhooks, with sample data
API & webhooks, with sample data. At the top, API keys: name, permission (Read + write or Read), only the start of the key, who created it and when it was last used; a revoked key stays with a Revoked note. Below, webhooks: the address, the chosen events, who it runs as, and Send test, Deliveries, Edit, Stop or Resume, Rotate secret, Delete. The second webhook was paused after repeated failed deliveries. At the bottom, the limits and How it works.

Who can use it?

The screen follows the Settings row in Roles & Permissions: viewing it needs view access, adding keys and webhooks needs edit access. Because a webhook sends conversation content out, setting one up or editing it also needs permission to view Conversations.

API keys

  1. Press Create key, give the key a name (which system will use it? e.g. “CRM sync”) and choose its permission: Read only or Read and write. Read and write also allows replying to conversations, adding internal notes and marking conversations as resolved.
  2. The key is shown only now, once: copy it and keep it somewhere safe. If you lose it, create a new one and revoke the old one.
  • A key works with the role permissions of the person who created it. Every request checks whether that person's role can see the equivalent panel screen. If their role narrows, so does the key; if the person is deactivated or deleted, the key stops working.
  • Revoke turns a key off immediately and for good; the row stays in the list as Revoked.
  • Up to 10 active keys per account and 120 requests per minute per key (requests over the limit get HTTP 429). The list shows when each key was last used.
  • SemAgent doesn't store the key itself, only a digest that is enough to check it.

Using the API

The address is https://semagent.ai/api/v1. Send the key in the Authorization: Bearer header with every request. Responses are JSON and carry the version they follow in api_version (currently 2026-10-26). Errors look like {"detail": {"code": "…", "message": "…"}}.

curl "https://semagent.ai/api/v1/conversations?status=active&limit=20" \
  -H "Authorization: Bearer $SEMAGENT_API_KEY"

curl -X POST "https://semagent.ai/api/v1/conversations/<id>/messages" \
  -H "Authorization: Bearer $SEMAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hi, your order ships tomorrow."}'
EndpointWhat it doesPermission
GET /meConnection test: the account's id and the key's permission.read
GET /conversationsThe conversation list. Filters: status, channel, limit (1–100, default 50), offset.read
GET /conversations/:idOne conversation, with its messages.read
POST /conversations/:id/messagesSends a reply ({"text": "…"}, at most 4,000 characters). It goes to the customer's channel the same way as Send in the panel, taking the conversation over if needed.read and write
POST /conversations/:id/notesAdds an internal note for the team ({"text": "…"}); the customer never sees it.read and write
POST /conversations/:id/resolveMarks the conversation as resolved.read and write
GET /contactsCustomers. q (search), limit (1–100), offset.read
GET /contacts/:idOne customer: name, email, conversation count, channels, tags, first and last seen.read

Webhooks

Add webhook: a public address starting with https:// and the events to be notified about. When those events happen, a signed request (POST, JSON) is sent to your address. Events from the moment you set it up are sent; past conversations aren't replayed.

EventWhen
conversation.createdA new conversation opens.
message.createdA message arrives or goes out (customer, team or AI). data.message is the message itself: the author (visitor, agent, ai, system, auto), the text (at most 4,000 characters) and attachments (name and size).
conversation.escalatedThe conversation is handed to the team.
conversation.resolvedThe conversation is resolved.
csat.submittedA customer gives a satisfaction rating; data.csat carries the score and the comment.

Every event's data.conversation sums up the conversation: channel, status, whether it was taken over, the customer's name and email, and when it was created, updated and resolved. A sample message.created request:

{
  "id": "4b9e0c52-7a1d-4f3e-9c06-2d8e5a1f7b30",
  "event": "message.created",
  "api_version": "2026-10-26",
  "tenant_id": "00000000-0000-4000-8000-000000000000",
  "created_at": "2026-10-26T08:41:10Z",
  "data": {
    "conversation": {
      "id": "0f3c7a91-5e2b-4d8a-b1c4-9a6e2d7f0c11",
      "channel": "whatsapp",
      "status": "active",
      "human_takeover": false,
      "customer": { "name": "Elif Yıldız", "email": null },
      "created_at": "2026-10-26T08:40:52Z",
      "updated_at": "2026-10-26T08:41:09Z",
      "resolved_at": null
    },
    "message": {
      "id": "7d21e6b0-3c4f-4a9e-8b57-1e0f9c2a6d48",
      "author": "visitor",
      "text": "When will my order ship?",
      "created_at": "2026-10-26T08:41:09Z",
      "attachments": []
    }
  }
}
  • Who it runs as: a webhook runs as the person who set it up (or last changed its address or events, or resumed it). If that person can no longer view Conversations or their account is off, the webhook stops by itself (Paused · creator lacks access); if you resume it, it runs as you.
  • Send test sends a webhook.test event with a real signature and shows the result (HTTP code and time); tests aren't recorded.
  • Deliveries lists recent deliveries: the event, status (Delivered, Will retry, Failed, Queued), attempt, HTTP code and time. Delivery records never keep the request body and are deleted after 7 days.
  • Success and retries: a 2xx response counts as delivered; the response is awaited for 10 seconds. A failed request is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours. After 20 failed deliveries in a row the webhook is paused; fix the issue and press Resume.
  • Safe addresses: requests only go to public addresses; addresses that point to a private network are refused and redirects aren't followed.
  • Up to 5 webhooks per account.

Verifying the signature

When a webhook is created, its secret is shown once; if you lose it, use Rotate secret. Every request carries three headers: SemAgent-Event (the event), SemAgent-Delivery (the delivery id) and SemAgent-Signature (t=<unix time>,v1=<signature>). The signature is the HMAC-SHA256, with your secret, of the header's t value, a dot and the request body joined together.

  • Compute the signature over the raw bytes of the body; don't parse and re-serialise the JSON first.
  • Reject requests whose t is older than 5 minutes.
  • The same event may rarely arrive twice: deduplicate by the SemAgent-Delivery id.
# Python
import hashlib, hmac, time

def verify(secret: str, header: str, body: bytes) -> bool:
    # header: SemAgent-Signature = "t=<unix>,v1=<hex>" ; body: the raw request body
    parts = dict(p.split("=", 1) for p in header.split(","))
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"]) and abs(time.time() - int(parts["t"])) < 300
// Node.js
const crypto = require("crypto")

function verify(secret, header, rawBody) {
  const p = Object.fromEntries(header.split(",").map((x) => x.split("=")))
  const expected = crypto.createHmac("sha256", secret).update(`${p.t}.`).update(rawBody).digest("hex")
  const same = expected.length === p.v1.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(p.v1))
  return same && Math.abs(Date.now() / 1000 - Number(p.t)) < 300
}
🔐

Webhooks and the API carry your customers' conversation content into your own systems. Protecting that data there, and describing it in your privacy notice, is your responsibility.

Was this page helpful? Send feedback