Skip to main content

Context Endpoints

A context endpoint is an HTTPS URL on your server that a Conversational Agent calls while it answers a customer. SendSeven sends a signed request that names the contact, your server looks them up in your own systems (CRM, shop, billing), and replies with JSON. You choose which values the agent may see through a response map. Only mapped values are passed on, and the agent sees them as "from your system".

Use it for facts that live outside SendSeven and change often, such as the plan, open orders, delivery status or the account balance.

Typical uses:

  • A shop agent on WhatsApp answers "where is my parcel?" with the real delivery state of the customer's last order.
  • A support agent sees the account status and open invoices count from your billing system, so a customer with a failed payment gets the right help first.
  • A B2B agent knows the customer's contract tier and account manager without anyone copying CRM data into SendSeven.

Context endpoints are one of several ways to give an agent customer data. See Customer context for the overview and Agent context sources for how an agent is configured to use them.

How it works​

  1. You create a context endpoint (URL, response map). SendSeven returns a signing secret once.
  2. In the agent's settings you add an HTTP context source that uses the endpoint (up to 3 per agent).
  3. When the agent answers a conversation whose contact is verified, SendSeven POSTs the contact's verified identifiers to your URL.
  4. Your server checks the signature and replies with a JSON object.
  5. SendSeven picks the mapped values and gives them to the agent. Responses are cached per contact for ttl_seconds.

If a contact has no verified identifier (for example an anonymous website visitor), no request is made. Verified identifiers are the conversation's own channel identifier (such as the WhatsApp number the customer writes from), the identifiers of a contact token and the identifiers of a signed identity. A value a visitor typed into the chat is never sent.

Create an endpoint​

curl -X POST https://api.sendseven.com/api/v1/context-endpoints \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "CRM lookup",
"url": "https://crm.example.com/sendseven/context",
"timeout_ms": 1500,
"ttl_seconds": 300,
"response_map": {
"plan": {"path": "customer.plan.name", "label": "Plan"},
"status": {"path": "customer.status", "label": "Account status"},
"last_order": {"path": "orders.0.id", "label": "Last order"}
}
}'

Response (201):

{
"id": "6d0c...",
"name": "CRM lookup",
"url": "https://crm.example.com/sendseven/context",
"timeout_ms": 1500,
"ttl_seconds": 300,
"response_map": {"plan": {"path": "customer.plan.name", "label": "Plan"}, "...": "..."},
"is_enabled": true,
"has_secret": true,
"secret_hint": "x9Qa",
"secret": "s7cs_...",
"created_at": "2026-10-07T12:00:00Z",
"updated_at": "2026-10-07T12:00:00Z"
}

Store secret on your server. It is shown only in this response, in the rotate-secret response, and in a PATCH response that changes the url. Later reads return just the last 4 characters (secret_hint).

Endpoints​

MethodPathScope
GET/api/v1/context-endpointssettings:read
GET/api/v1/context-endpoints/{id}settings:read
POST/api/v1/context-endpointssettings:update
PATCH/api/v1/context-endpoints/{id}settings:update (+ contacts:read when url changes)
DELETE/api/v1/context-endpoints/{id}settings:update
POST/api/v1/context-endpoints/{id}/rotate-secretsettings:update
POST/api/v1/context-endpoints/{id}/testsettings:update + contacts:read
  • List responses use the standard {"items": [...], "pagination": {...}} shape (page, page_size up to 100).
  • DELETE returns {"success": true, "id": "..."}. Agents stop calling a deleted endpoint immediately.
  • PATCH is partial. "response_map": null clears the map. Set "is_enabled": false to pause an endpoint without deleting it.
  • Changing the url issues a new signing secret. A PATCH that changes url also needs contacts:read, is not available to OAuth apps (403 oauth_not_allowed), and returns the new secret once (like rotate-secret). The old secret stops working immediately, so deploy the new secret together with the new URL. A PATCH that leaves url unchanged returns no secret.
  • OAuth apps cannot create endpoints, rotate secrets or change an endpoint's URL (403 oauth_not_allowed), because these responses contain the secret. Use an API key or the SendSeven app.
  • Rotating the secret makes the old one invalid immediately and clears cached responses.

Test an endpoint​

curl -X POST https://api.sendseven.com/api/v1/context-endpoints/{id}/test \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contact_id": "CONTACT_ID"}'

The test makes one real, uncached request for a contact of your workspace (even if the endpoint is disabled) and returns:

{
"ok": true,
"error_code": null,
"data": {"plan": "Pro", "status": "active", "last_order": "A-1042"},
"request_payload": {"tenant_id": "...", "conversation_id": null, "contact": {"id": "...", "identifiers": [{"type": "email", "value": "[email protected]"}], "channel_type": null}, "test": true}
}

data is exactly what an agent would see. A test request includes the contact's stored contact methods as identifiers and carries "test": true. Live agent requests carry only verified identifiers and no test key.

The request your server receives​

POST /sendseven/context HTTP/1.1
Content-Type: application/json
X-SendSeven-Signature: t=1791374400,v1=5f2c...e81a

{"contact":{"channel_type":"whatsapp","id":"c0a1...","identifiers":[{"type":"phone","value":"+4915112345678"}]},"conversation_id":"9b7e...","tenant_id":"2f3d..."}
FieldDescription
tenant_idYour SendSeven workspace ID
conversation_idThe conversation the agent is answering
contact.idSendSeven contact ID
contact.identifiersVerified identifiers only, as {"type", "value"} (for example phone, email, whatsapp_id, external_id; at most 10)
contact.channel_typeChannel of the conversation, for example whatsapp, live_chat, email

Verify the signature​

The header is X-SendSeven-Signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of the string "{t}.{raw request body}", keyed with your endpoint secret (UTF-8). Always compute it over the raw body bytes you received, before parsing JSON. Reject requests older than 5 minutes.

import hashlib, hmac, time

def verify(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
t, sig = parts.get("t"), parts.get("v1")
if not t or not sig or abs(time.time() - int(t)) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
const crypto = require("crypto");

function verify(secret, header, rawBody, tolerance = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
const t = parts.t, sig = parts.v1;
if (!t || !sig || Math.abs(Date.now() / 1000 - Number(t)) > tolerance) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest("hex");
return sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

Your response and the response map​

Reply with 200 and a JSON object (max 16 KB). Return only what you are comfortable showing to the agent. Anything that is not mapped is dropped.

{
"customer": {"plan": {"name": "Pro"}, "status": "active"},
"orders": [{"id": "A-1042", "state": "shipped"}]
}

With the map from the example above, the agent receives Plan: Pro, Account status: active, Last order: A-1042.

  • Each path is a dotted path into your JSON. Use a number for a list index (orders.0.id).
  • Missing paths are skipped. Long values are shortened (strings to 300 characters).
  • If your server is unknown to the contact, reply 200 with {}.

Non-200 status codes, redirects, timeouts, invalid JSON and bodies over 16 KB count as failures. The agent then answers without this source. It never fails the reply. Redirects are not followed.

Limits​

SettingLimit
urlhttps:// only, port 443, max 2048 characters. It must resolve to a public address (private, loopback and internal hosts are rejected).
timeout_ms200 to 3000 (default 1500). Values outside the range are clamped.
ttl_seconds0 to 3600 (default 300; 0 turns the cache off). Values outside the range are clamped.
response_mapUp to 30 keys. A key starts with a letter and uses letters, digits and _ (max 64). path is up to 200 characters and 8 segments. label is up to 60 characters.
name1 to 100 characters
Endpoints per workspace50
HTTP sources per agent3. Per-agent timeout_ms / ttl_seconds can only lower the endpoint's values.

Validation errors return 422 with a code, for example url_not_https, url_blocked, url_unresolvable, invalid_url, too_many_response_map_keys, invalid_response_map_path or invalid_response_map_label.

See what an agent knows about a contact​

GET /api/v1/conversations/{conversation_id}/contact-context (scopes conversations:read + contacts:read) returns the contact data the agent sees for a conversation, with a verified flag for every value:

{
"assurance": "token",
"grant": {"standard": {"email": "[email protected]"}, "custom": {"customer_tier": "gold"}, "context": {"plan": "Pro"}, "expires_at": "2026-10-08T12:00:00Z"},
"fields": [{"key": "customer_tier", "name": "Customer tier", "value": "gold", "source": "token", "verified": true}],
"methods": [{"type": "email", "value": "[email protected]", "verified": true}]
}

grant is null when no contact token is active. This endpoint does not call your context endpoints. It returns 404 when the conversation does not exist or is outside the caller's inboxes.