Security and Authentication

Security model for the Weenx Integration API — workspace-scoped API keys, scopes, idempotency, rate limits, plan capability gating, transport security, webhook verification, and safe logging.

The Integration API is designed for trusted server-to-server traffic only.

Do not call this API directly from browser code, mobile applications, public JavaScript bundles, or customer-facing clients. Every request should originate from controlled backend infrastructure where API keys, webhook secrets, retry behaviour, logs, and egress can be managed securely.

Security model

The Integration API security model is based on:

  • workspace-scoped API keys
  • per-endpoint scope requirements
  • idempotency for state-changing writes
  • a per-key request rate limit
  • plan capability gating for premium flows
  • a payout destination whitelist, with execution held out of the API surface
  • reverse-proxy-aware client-IP resolution
  • HTTPS redirection, HSTS, and response security headers
  • signed outbound webhook verification
  • safe logging and stable, display-safe error messages

The API is intentionally workspace-scoped. The workspace is resolved from the API key. Do not send a workspace identifier in a route, query string, or request body — it is never read, and the request always acts on the workspace the key belongs to.

Public and protected routes

Three routes are unauthenticated:

  • GET / — a liveness object, { "Name": "Weenx IntegrationApi", "Status": "OK", ... }
  • GET /health{ "Status": "Healthy", ... }
  • GET /openapi/v1.json — the machine-readable OpenAPI document, so integrators can generate typed clients without a key

Every route under v1/... requires the X-Api-Key header.

API key authentication

Every protected request must include:

X-Api-Key: YOUR_WORKSPACE_API_KEY

Example:

curl --request GET \
  --url https://api.weenx.com/v1/me \
  --header 'X-Api-Key: YOUR_WORKSPACE_API_KEY'

Each request is validated against all of the following:

  • the key exists and is active
  • the key has not been revoked or expired
  • the key resolves to a workspace, and that workspace is active
  • the endpoint's required scope is granted to the key

When the header is missing, empty, or whitespace-only, or when the key fails any of these checks, the request is rejected with 401 Unauthorized and the standard envelope. When authentication succeeds, the request is bound to the resolved workspace for its whole lifetime — which is why no endpoint accepts a workspace identifier.

API key failure behaviour

401 Unauthorized

Returned when the X-Api-Key header is missing or empty, or when the key is invalid, inactive, expired, or cannot resolve an active workspace.

{
  "success": false,
  "message": "Missing X-Api-Key header."
}

403 Forbidden

Returned when the key authenticates but is not allowed to perform the operation. There are two distinct causes, distinguishable by the response:

  • Missing scope — the key does not grant the scope the endpoint requires. This response carries only success and message, with no code:

    {
      "success": false,
      "message": "API key is missing the required scope 'payouts:write'."
    }
  • Plan capability denied — the endpoint is gated behind a plan capability the workspace does not have (for example webhooks, auto-convert, dedicated wallets). This response carries the plan.capability_denied code:

    {
      "success": false,
      "message": "Your plan does not include this capability.",
      "code": "plan.capability_denied"
    }

429 Too Many Requests

Returned when the per-key rate limit is exceeded. See Rate limiting.

Scopes

An API key carries a list of granted scopes, and each endpoint requires exactly one. Scopes follow a resource:action shape. A key that holds the wildcard * satisfies every endpoint; issue narrow, per-duty keys for least-privilege deployments.

GET /v1/me requires no scope beyond a valid key — call it first and compare grantedScopes against the table below before you enable a flow.

Endpoint groupRead scopeWrite scope
GET /v1/me(none — valid key only)
Catalog (/v1/catalog/...)catalog:read
Balances (/v1/balances)balances:read
Invoices (/v1/invoices...)invoices:readinvoices:write
Refunds & reversals (/v1/invoices/{id}/refunds, /reversals)invoices:readinvoices:write
Charges (/v1/charges...)charges:readcharges:write
Payments (/v1/payments...)payments:read
Payouts & whitelist (/v1/payouts..., /v1/payout-whitelist...)payouts:readpayouts:write
Transfers (/v1/transfers)transfers:readtransfers:write
Addresses (/v1/addresses..., invoice addresses)addresses:readaddresses:write
Customers (/v1/customers...)customers:readcustomers:write
Links (quick links, invoice-link reads)links:readlinks:write
No-code buttons (/v1/buttons...)buttons:readbuttons:write
Subscriptions (/v1/subscriptions..., plans)subscriptions:readsubscriptions:write
On-ramp (/v1/onramp...)onramp:readonramp:write
Off-ramp (/v1/offramp...)offramp:readofframp:write
Auto-convert (/v1/auto-convert...)autoconvert:readautoconvert:write
Settlement (/v1/settlement...)settlement:readsettlement:write
Webhooks (/v1/webhooks...)webhooks:readwebhooks:write
Billing (/v1/billing...)billing:read
Reports (/v1/reports...)reports:read

Two cross-resource cases are worth calling out:

  • Creating an invoice payment link (POST /v1/invoices/{invoiceId}/links) requires invoices:write, while listing invoice links (GET /v1/invoices/{invoiceId}/links) requires links:read.
  • Refunds and reversals are invoice-scoped and reuse the invoice scopes — invoices:read to read or quote, invoices:write to queue one.

Plan capability gating

Beyond scopes, several write flows are gated by your plan's capabilities, independent of the scope your key holds. A denied capability returns 403 with the code plan.capability_denied. The gated flows are:

  • Webhooks — creating a webhook endpoint requires the webhooks capability.
  • Auto-convert — creating or updating an auto-convert rule requires the autoConvert capability.
  • Hosted checkout — no-code button writes require the hostedCheckout capability.
  • Dedicated wallets — the on-chain settlement configuration (POST /v1/settlement/config) requires the dedicatedWallets capability.
  • Custom settlement schedules — settlement threshold policies (POST /v1/settlement/policies) require the customSettlementSchedules capability.
  • Maker-checker payouts — dual-approval payout enforcement is a plan capability applied during payout approval.

Read your current capabilities from GET /v1/billing/plan and treat a plan.capability_denied response as a configuration/entitlement condition, not a transient error to retry. See Terms and Enterprise Custody for how the dedicated-wallet and custom-settlement capabilities map to the Enterprise custody model.

Idempotency

State-changing writes can be made safe to retry with an idempotency key. Placement is fixed per endpoint:

  • Idempotency-Key headerPOST /v1/invoices.
  • idempotencyKey body field — payouts (POST /v1/payouts), internal transfers (POST /v1/transfers, where it is required), and ramp order creation (POST /v1/onramp/orders, POST /v1/offramp/orders).

An idempotency key may be up to 128 characters; an over-length value is rejected. A key is scoped to the workspace, the API key, the HTTP method, and the request path, so the same key on two different endpoints is two different reservations.

Behaviour for the same workspace, key, method, and path:

  • First use — the operation runs, and its response is stored.
  • Same key, same payload — the stored response (status and body) is replayed verbatim.
  • Same key, different payload — rejected with 409 Conflict; two different operations can never share one key.
  • First attempt still in flight — rejected with 409 Conflict until it finishes.

Operational detail:

  • retained for 24 hours after the write, after which the key is free to reuse
  • payload matching uses a SHA-256 hash of the serialized request body
  • an in-flight reservation whose operation fails is released so the client can retry with the same key

Client rule: reuse the same key only for the same logical business action with the same payload. Do not mint a new key for a write whose final state is unknown — retry with the same key, or reconcile through the read endpoints and webhooks.

Idempotency key design

Use a stable, business-derived value:

  • order-{orderId} for an invoice
  • payout-{orderId} for a payout
  • transfer-{settlementRunId} for an internal transfer

Avoid random keys for retried writes, reusing one key for different payloads, and running parallel write attempts for the same business action.

Rate limiting

Requests are rate-limited per API key. The default allowance is 300 requests per 60-second window per key. When a key is exceeded, the request is rejected with 429 Too Many Requests:

{
  "success": false,
  "message": "Rate limit exceeded. Retry shortly."
}

Recommended client behaviour: stop immediate retries, back off with exponential delay and jitter, preserve the same idempotency key for retried writes, and reduce parallelism if throttling repeats. The limiter partitions by the authenticated key, so separate keys have separate budgets.

Transport security

Production traffic should use HTTPS end to end. Outside development the API host applies HTTPS redirection and HSTS and honours the reverse proxy's forwarded scheme and client address, so the client IP used for security decisions is the real caller rather than a proxy hop. Your infrastructure should not weaken these controls at a proxy, CDN, or gateway layer.

Webhook verification

Outbound webhooks are signed with HMAC-SHA256. Before you parse or act on a delivery, verify its signature against the raw request body. Each delivery carries X-Weenx-Signature, X-Weenx-Timestamp, and X-Weenx-Event; the signed material is the exact string {timestamp}.{rawBody}. The full verification procedure, header set, and event catalog are documented in Webhooks.

Safe logging

Do not log:

  • full API keys
  • webhook signing secrets
  • full request bodies for payout, transfer, or settlement writes that contain destination addresses you treat as sensitive
  • any header that carries a credential

Safe to log:

  • workspace environment id
  • an API-key id reference, never the key itself
  • invoice id, payout request id, operation id
  • HTTP status code and, on failures, the envelope code
  • idempotency key reference
  • webhook delivery id, event name, and attempt number

Hash or redact any value that could be used as a credential.

Errors you pass on

A failure response carries an HTTP status, a display-safe message, and — for typed domain errors — a machine-readable code such as invalid_request, access_denied, plan.capability_denied, or internal_error. Field-level validation failures add an errors object keyed by field name. When you surface an error to your own users or downstream systems, pass through the documented code and message rather than raw exception text from your own stack. Drive control flow from the HTTP status and code; treat message as display text whose wording can change.

Security checklist

Before production use, confirm that:

  • API keys are stored only in backend secret storage and are not exposed in frontend or mobile applications
  • webhook signing secrets are stored securely and never logged
  • each state-changing write uses a stable idempotency key, and timeout retries reuse it
  • 429 handling backs off and preserves the idempotency key
  • webhook signatures are verified against the raw request body, and delivery ids are deduplicated
  • payout destinations are whitelisted, and payout execution stays operator-gated in the console
  • plan capability gating is handled — a plan.capability_denied 403 is treated as an entitlement condition
  • logs redact credentials and other sensitive data
  • operational alerts exist for repeated 401, 403, 409, 429, and 500 responses

Did this page help you?