Security

How Jelliu protects your workspace, and what your integration should do on its side.
View as Markdown

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

Jelliu doesYou do
Serves the API over HTTPS with HSTS.Call https://api.jelliu.co only, and never disable certificate verification.
Stores API keys and widget keys only as hashes, and shows them once.Keep keys in a secrets manager, never in web pages, mobile apps or repositories.
Scopes every request to the workspace of the credential.Use one key per integration with the least scope it needs.
Signs every webhook delivery with a per-webhook secret.Verify X-Webhook-Signature-V2 and the timestamp before trusting a delivery.
Refuses webhook URLs that point to private networks.Serve your webhook endpoint over HTTPS.
Records mutations in a tamper-evident audit log.Review it, and subscribe to audit.log_recorded if you need it in your SIEM.

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-Policy with frame-ancestors 'none', X-Frame-Options: DENY and Referrer-Policy: strict-origin-when-cross-origin. API responses cannot be framed by another site.
  • No fingerprinting headers. X-Powered-By is disabled and the Server header 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:

PropertyBehavior
StorageOnly a SHA-256 hash of the key is stored. The full value is shown once, at creation.
Scopesread, write or full, enforced on every request by HTTP method, and again on routes restricted to administrator or owner roles.
RevocationImmediate on the instance that handles it; allow about 10 seconds for every instance to refuse the key.
ExpiryOptional. An expired key is refused with 401.
Credential managementCreating or revoking API keys, rotating a webhook secret, changing the workspace identity policy (MFA and SSO) and pinning data residency all require a signed-in person in the dashboard. An API key is refused with 403 even with the full scope, so a leaked key cannot mint new keys or disable the controls meant to contain it.

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:

SurfaceCredentialProtection
/widget/*, /widget-embed/*Public widget ID, or the secret widget API key from serversAllowed origins, per-IP, per-widget, per-workspace and daily limits. See Web chat.
/mcp (https://mcp.jelliu.co)API key or OAuth tokenPer-IP limit plus the workspace’s API limits.
/openapi.yaml, /openapi.public.yamlNoneRead-only documentation.

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-key header. It is never accepted in a query string, where it would end up in logs and Referer headers.
  • 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:

ControlBehavior
Body sizeJSON and form bodies are limited to 1 MB. File uploads have their own per-endpoint limits.
JSON depthBodies nested deeper than 32 levels are rejected before parsing.
Duplicate query parameters?status=a&status=b is read as status=a: the first value wins.
HTML in text fieldsOn POST, PUT, PATCH and DELETE requests with JSON or form bodies (webhook configuration excepted), HTML tags, script content and javascript: URLs are stripped from string values before they reach the application, and an unclosed < removes the rest of the string. Send plain text.
Input validationBodies, queries and path parameters are validated against strict schemas; failures return 400 VALIDATION_FAILED. See Errors.
Error bodies5xx responses never include internal details. The cause is logged on Jelliu’s side only.

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 localhost name, 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-*, Host or Content-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 with from and to as 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 full scope.
  • Streaming. Subscribe a webhook to audit.log_recorded to 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

  • 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.

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.

  • Verify X-Webhook-Signature-V2 against the raw body and check the timestamp.
  • Acknowledge with 2xx quickly 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.

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.

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.

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.