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
- You create a context endpoint (URL, response map). SendSeven returns a signing secret once.
- In the agent's settings you add an HTTP context source that uses the endpoint (up to 3 per agent).
- When the agent answers a conversation whose contact is verified, SendSeven POSTs the contact's verified identifiers to your URL.
- Your server checks the signature and replies with a JSON object.
- 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
| Method | Path | Scope |
|---|---|---|
GET | /api/v1/context-endpoints | settings:read |
GET | /api/v1/context-endpoints/{id} | settings:read |
POST | /api/v1/context-endpoints | settings: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-secret | settings:update |
POST | /api/v1/context-endpoints/{id}/test | settings:update + contacts:read |
- List responses use the standard
{"items": [...], "pagination": {...}}shape (page,page_sizeup to 100). DELETEreturns{"success": true, "id": "..."}. Agents stop calling a deleted endpoint immediately.PATCHis partial."response_map": nullclears the map. Set"is_enabled": falseto pause an endpoint without deleting it.- Changing the
urlissues a new signing secret. APATCHthat changesurlalso needscontacts:read, is not available to OAuth apps (403 oauth_not_allowed), and returns the newsecretonce (like rotate-secret). The old secret stops working immediately, so deploy the new secret together with the new URL. APATCHthat leavesurlunchanged returns nosecret. - 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..."}
| Field | Description |
|---|---|
tenant_id | Your SendSeven workspace ID |
conversation_id | The conversation the agent is answering |
contact.id | SendSeven contact ID |
contact.identifiers | Verified identifiers only, as {"type", "value"} (for example phone, email, whatsapp_id, external_id; at most 10) |
contact.channel_type | Channel 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
pathis 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
200with{}.
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
| Setting | Limit |
|---|---|
url | https:// only, port 443, max 2048 characters. It must resolve to a public address (private, loopback and internal hosts are rejected). |
timeout_ms | 200 to 3000 (default 1500). Values outside the range are clamped. |
ttl_seconds | 0 to 3600 (default 300; 0 turns the cache off). Values outside the range are clamped. |
response_map | Up 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. |
name | 1 to 100 characters |
| Endpoints per workspace | 50 |
| HTTP sources per agent | 3. 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.