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_KEYExample:
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
401 UnauthorizedReturned 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
403 ForbiddenReturned 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
successandmessage, with nocode:{ "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_deniedcode:{ "success": false, "message": "Your plan does not include this capability.", "code": "plan.capability_denied" }
429 Too Many Requests
429 Too Many RequestsReturned 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 group | Read scope | Write scope |
|---|---|---|
GET /v1/me | (none — valid key only) | — |
Catalog (/v1/catalog/...) | catalog:read | — |
Balances (/v1/balances) | balances:read | — |
Invoices (/v1/invoices...) | invoices:read | invoices:write |
Refunds & reversals (/v1/invoices/{id}/refunds, /reversals) | invoices:read | invoices:write |
Charges (/v1/charges...) | charges:read | charges:write |
Payments (/v1/payments...) | payments:read | — |
Payouts & whitelist (/v1/payouts..., /v1/payout-whitelist...) | payouts:read | payouts:write |
Transfers (/v1/transfers) | transfers:read | transfers:write |
Addresses (/v1/addresses..., invoice addresses) | addresses:read | addresses:write |
Customers (/v1/customers...) | customers:read | customers:write |
| Links (quick links, invoice-link reads) | links:read | links:write |
No-code buttons (/v1/buttons...) | buttons:read | buttons:write |
Subscriptions (/v1/subscriptions..., plans) | subscriptions:read | subscriptions:write |
On-ramp (/v1/onramp...) | onramp:read | onramp:write |
Off-ramp (/v1/offramp...) | offramp:read | offramp:write |
Auto-convert (/v1/auto-convert...) | autoconvert:read | autoconvert:write |
Settlement (/v1/settlement...) | settlement:read | settlement:write |
Webhooks (/v1/webhooks...) | webhooks:read | webhooks: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) requiresinvoices:write, while listing invoice links (GET /v1/invoices/{invoiceId}/links) requireslinks:read. - Refunds and reversals are invoice-scoped and reuse the invoice scopes —
invoices:readto read or quote,invoices:writeto 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
webhookscapability. - Auto-convert — creating or updating an auto-convert rule requires the
autoConvertcapability. - Hosted checkout — no-code button writes require the
hostedCheckoutcapability. - Dedicated wallets — the on-chain settlement configuration (
POST /v1/settlement/config) requires thededicatedWalletscapability. - Custom settlement schedules — settlement threshold policies (
POST /v1/settlement/policies) require thecustomSettlementSchedulescapability. - 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-Keyheader —POST /v1/invoices.idempotencyKeybody 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 Conflictuntil 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 invoicepayout-{orderId}for a payouttransfer-{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
429handling 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_denied403 is treated as an entitlement condition - logs redact credentials and other sensitive data
- operational alerts exist for repeated
401,403,409,429, and500responses
Updated 6 days ago