Skip to main content
Every call between your server and ENSC is protected by three independent layers on top of TLS. The official SDK (@ensc/sdk) applies all of them automatically; this page explains what they are so you know what you are relying on.

Your credentials

When you generate keys in the dashboard you receive three secrets, with their identifiers, for each environment (Sandbox, Live): Plus your merchant id (mrc_…), which is not secret. The secrets are shown once, at generation. ENSC keeps only a hash of the API key and only the public half of your signing key; it stores your encryption key wrapped under its own key so it can decrypt your requests. Nobody at ENSC can recover a lost secret: rotate instead. See Generating keys.

Authorization: the API key

Every request carries your API key. Every write also carries an Ed25519 signature. @ensc/sdk adds both automatically.
  • The key’s prefix encodes the environment (ensc_test_ / ensc_live_) and type (sk secret, rk restricted, pk publishable). A test key cannot touch live resources and vice versa (ENSC_TEST_LIVE_MISMATCH).
  • X-ENSC-API-Version pins the contract you were built against. The current version is 2026-09-15. See API versioning.
  • X-ENSC-Key-Id names your signing key. On writes it is the key ENSC verifies your signature with; on reads it is the key ENSC seals the response to. Send it on every request.
  • Live keys are accepted only from allowlisted IPs (ENSC_IP_NOT_ALLOWED otherwise), and a live key whose allowlist is empty is refused on every request (ENSC_IP_ALLOWLIST_REQUIRED). See IP allowlist.

Permissions (scopes)

Each key carries a fixed list of scopes, chosen when it is generated. An endpoint refuses a key without the scope it needs (403 ENSC_INSUFFICIENT_SCOPE). The keys the dashboard generates carry all four by default. A secret key also manages webhook endpoints and reads the event log without a dedicated scope. A restricted key needs webhooks:read to list endpoints and read events, and webhooks:manage to create, change, test or delete endpoints. api-keys:read and api-keys:manage are reserved and not yet required by any route. Publishable keys can hold only balances:read: they can read balances and nothing else, their responses are not sealed, and they are refused on every management route (credential lists, webhook endpoints, events and test data). Issuing, rotating and revoking any key, and editing IP allowlists or CORS origins, are dashboard actions and are refused from API keys (403 ENSC_DASHBOARD_ONLY).

Layer 1: encrypted requests

The body of every write (creating a conversion, reporting its transaction, requesting a voucher or payout, resolving a bank account, building a transfer, webhook endpoint management) is encrypted with AES-256-GCM under your encryption key and sent as an envelope:
The encryption is bound to the HTTP method, the path, your merchant id and the key id, so a captured envelope cannot be replayed against another endpoint or another account. ENSC decrypts inside its application code, after TLS termination, and validates the plaintext there. Plaintext bodies are refused; there is no way to turn encryption off.

Layer 2: signed requests

Every write also carries an Ed25519 signature over the method, path, query, a hash of the (encrypted) body, a timestamp, a single-use nonce, your merchant id and your idempotency key. ENSC verifies it against your registered public key, rejects timestamps more than five minutes off, and rejects any nonce it has seen before. A request cannot be altered in transit or replayed.

Layer 3: sealed and signed responses

Every successful response is encrypted to your signing key using HPKE (RFC 9180: X25519, HKDF-SHA256, ChaCha20-Poly1305) and signed by ENSC:
with headers X-ENSC-Signature, X-ENSC-Key-Id, X-ENSC-Timestamp, X-ENSC-Request-Id. The SDK checks ENSC’s signature against the public keys published at GET /v1/.well-known/ensc-public-keys.json before it opens anything, so a forged or substituted response is never parsed. Only your signing private key can open the body: someone holding just your API key learns nothing from the responses. Error responses are not sealed, so a 4xx or 5xx is always readable. The exact formats of all three layers are in Encrypting and signing requests.

What each stolen credential buys an attacker

If you suspect any credential has leaked, rotate it from the dashboard. Rotation keeps the old key working for 24 hours so you can roll your deployment; revocation is immediate.

Your wallet key stays yours

ENSC never holds or asks for a wallet private key. Every on-chain action (a conversion, a transfer) is returned as unsigned calldata, { from, to, data, value: "0", chainId }, that your own wallet signs and broadcasts. Conversions are additionally gated by a voucher that ENSC signs for the exact wallet, amounts and deadline you asked for; the converter contract refuses a voucher presented by any other wallet, reused, or presented after its deadline. See Conversions. Bank account details you give for a payout are stored encrypted; ENSC’s responses and webhooks show only the last four digits. A payout is sent only to the account your conversion committed to on chain.

Webhooks

Webhooks ENSC sends to you are signed with the same Ed25519 key that signs responses, over the webhook id, the timestamp and the SHA-256 of the raw body (ENSC-WH-V1). There is no shared secret to store or rotate. Verify them with EnscClient.constructEvent() and the keys from /v1/.well-known/ensc-public-keys.json (EnscClient.fetchPublicKeys() loads them; the verifier picks the one named by X-ENSC-Key-Id) before acting on a delivery, and de-duplicate on the event id. Webhook payloads never carry bank account numbers. The receiver guide is Webhooks.

Where this is implemented

All of the above is standard cryptography (AES-GCM, Ed25519, HPKE) implemented on audited primitives; nothing is proprietary. The SDK and the API share the same implementation, and the API’s test suite runs the SDK end to end against it.