Security
This page describes the security controls that apply to every integration: transport, authentication, workspace isolation, credential storage, webhook signing and auditing. Each statement describes behavior of the API as it runs today. Where a control needs something from you, the page says so.
Shared responsibility at a glance
Transport
- HTTPS only. API responses carry
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload, so browsers that have seen it refuse plain HTTP to the domain for a year. - Security headers. Responses served by the API include a restrictive
Content-Security-Policywithframe-ancestors 'none',X-Frame-Options: DENYandReferrer-Policy: strict-origin-when-cross-origin. API responses cannot be framed by another site. - No fingerprinting headers.
X-Powered-Byis disabled and theServerheader is removed.
Authentication
Every /api/* request is authenticated with a workspace API key (jl_ followed by 64 hexadecimal characters) in the Authorization: Bearer header. The full details are in Authentication and API keys; the security-relevant properties are:
A full key acts as a workspace administrator. Treat it like an admin password: keep it server-side, rotate it on staff changes, and prefer read or write keys wherever they are enough.
People in the dashboard
Workspace owners can require multi-factor authentication and configure single sign-on for the people who sign in to the dashboard. These policies apply to human sessions only. Machine credentials (API keys, webhooks) have no second factor and are not affected, which is another reason to scope keys narrowly.
MCP clients
The MCP server accepts the same API keys, or OAuth 2.1 with PKCE (S256) for clients such as Claude.ai. OAuth access tokens expire after one hour and never carry more access than the person who approved the connection. Every tool call goes through the same authentication, scopes, rate limits and audit log as a direct API request.
Workspace isolation
Jelliu is multi-tenant. Your data is separated from other customers’ data by the following controls:
- The workspace comes from the credential, never from the request. An API key belongs to exactly one workspace. No parameter, header or body field lets a caller select a different workspace.
- Queries are scoped, and the build enforces it. Tables that hold customer data carry the workspace identifier, and application reads, updates and deletes on them filter by it. A blocking check in Jelliu’s build pipeline analyzes the code and fails the build when such a query lacks that filter; the few intentional exceptions, such as matching an inbound provider callback to its workspace, must each carry a written justification.
- Foreign IDs look like missing IDs. Asking for a resource that belongs to another workspace returns
404, exactly like an ID that does not exist, so IDs cannot be used to probe other workspaces. - Encryption bound to the workspace. Sensitive fields such as third-party credentials are encrypted with AES-256-GCM, with the workspace identifier as additional authenticated data. A value copied into another workspace’s row fails authentication and cannot be decrypted.
- Rate limits per workspace. Once authenticated, limits are counted per workspace, so another customer’s traffic cannot exhaust your budget. See Rate limits.
Browser access and CORS
The REST API under /api/* is meant to be called from servers. Its CORS policy only admits Jelliu’s own dashboard origins, so a browser page on your domain cannot call /api/* directly, and it should not: doing so would expose a workspace key to every visitor.
A few surfaces are designed for browsers and accept any origin, without cookies or credentials:
Widget credentials
- The widget ID is public by design and is the only credential the embed uses.
- The widget API key is shown once, stored only as a keyed HMAC-SHA256 digest, and accepted only in the
x-widget-api-keyheader. It is never accepted in a query string, where it would end up in logs andRefererheaders. - Each widget has an explicit list of allowed origins; wildcards are rejected. The origin check stops other websites from embedding your chat. Because a non-browser client can claim any origin, it is paired with rate limits and a daily message cap that fail closed.
Request hardening
These limits apply to requests you send and can explain unexpected 400 responses:
Webhooks
Signed deliveries
Every outbound webhook delivery is signed with a secret unique to that webhook (whsec_...). X-Webhook-Signature-V2 is an HMAC-SHA256 over the timestamp and the raw body, so a captured delivery cannot be replayed outside your tolerance window. The secret is shown once, and rotating it requires a dashboard session. Verify every delivery before acting on it.
Reject deliveries whose signature does not match or whose X-Webhook-Timestamp is outside your tolerance window, and compare signatures in constant time. The CRM sync recipe has a complete receiver.
Protection against server-side request forgery
Jelliu will not send webhooks into private networks, including its own:
- When you create or update a webhook, its URL is resolved and refused if it points to a loopback, private, link-local, carrier-grade NAT or unique-local address, a cloud metadata endpoint, a
localhostname, a sensitive port (such as SSH, SMTP, DNS or common database ports) or a scheme other than HTTP and HTTPS. - At delivery time the connection is pinned to the address that passed validation, so a DNS record changed after the check cannot redirect the request to an internal host.
- Custom headers you configure cannot replace
X-Webhook-*,HostorContent-Length.
To receive webhooks during local development, expose your machine through a public HTTPS tunnel; localhost URLs are refused.
Inbound provider callbacks
Callbacks that Jelliu receives from its own providers (telephony, voice and payments) are verified by their providers’ signatures before they are processed. They are not part of the public API.
Audit log
Every mutating /api request (POST, PUT, PATCH, DELETE) made by a person, an API key or the MCP server is recorded in the workspace audit log, together with the few reads that hand out credentials.
- Redaction. Secrets (passwords, tokens, API keys, signing secrets, authorization headers) and contact-identifying fields are redacted before an entry is written.
- Tamper evidence. Entries form a hash chain keyed with a server-side secret.
GET /api/audit/verify-chain, optionally withfromandtoas ISO 8601 timestamps, recomputes the chain for your workspace and reports any break. - Access. Reading the audit log and verifying the chain are restricted to the workspace owner, which for API keys means the
fullscope. - Streaming. Subscribe a webhook to
audit.log_recordedto forward entries to your SIEM. It is not included in"*"; name it explicitly.
See Compliance for the audit log API, retention settings and data subject requests.
Data residency
GET /api/settings/data-residency returns your workspace’s effective data residency and the chain of processors it depends on. Pinning a region is a one-time, owner-only action in the dashboard. Details are in Compliance.
Best practices for your integration
Store and scope credentials
- One API key per integration, named after it, with the smallest scope that works.
- Keep keys in a secrets manager or environment variables injected at runtime. Never commit them, log them or send them to a browser.
- Set an expiry on keys used by temporary projects or contractors.
- The workspace can hold 25 active keys; revoke keys you no longer use.
Rotate without downtime
Create the new key in the dashboard, deploy it alongside the old one, confirm traffic succeeds with the new key, then revoke the old key. Allow about 10 seconds after revocation before assuming every instance refuses it. See API keys.
Harden your webhook receiver
- Verify
X-Webhook-Signature-V2against the raw body and check the timestamp. - Acknowledge with
2xxquickly and process asynchronously. - Deduplicate retries: a retry carries the same timestamp and body, and therefore the same signature.
- Ignore fields you do not recognize instead of failing.
Keep browsers away from workspace keys
Browser and mobile code should use the public widget ID and the /widget/* endpoints, or call your own backend, which holds the key. Never embed a jl_ key or a widget API key in client-side code.
Treat AI tools that act as operations
Assistants connected over MCP can place calls and send messages. Use a read key for reporting assistants and configure clients to confirm before running tools that act. See AI assistant via MCP.
Respond to a leaked key
Revoke the key in the dashboard immediately, create a replacement, and review the audit log for actions taken with the leaked key since it was exposed. If a webhook secret may have leaked, rotate it from the dashboard and deploy the new value right away: there is no overlap period.
Reporting a vulnerability
If you believe you have found a security vulnerability in Jelliu, email support@jelliu.com with “Security” in the subject line. Include the affected endpoint or component, steps to reproduce and the impact you observed.
Please do not access, modify or delete data that does not belong to you, do not degrade the service for other customers, and give us reasonable time to fix the issue before disclosing it publicly.

