One Link Wallet API

Secure, standards-based REST API for creating and tracking transfers. Highlights: OAuth 2.1 Signed responses Idempotency RFC 7807 errors Webhooks

Base URLs
Sandbox: https://api.sandbox.onelinkwallet.com/v1
Production: https://api.onelinkwallet.com/v1
Always use HTTPS (TLS 1.2+, prefer 1.3). Send Accept: application/json, a descriptive User-Agent, and a unique X-Request-Id per call.
OpenAPI 3.1: GET /openapi.json • JWKS: GET /.well-known/jwks.json

Authentication (OAuth 2.1 Client Credentials)

# Request token (RFC 6749/8252; OAuth 2.1 profile)
curl -u CLIENT_ID:CLIENT_SECRET \
  -d 'grant_type=client_credentials&scope=transfers:create transfers:read webhooks:manage' \
  https://api.sandbox.onelinkwallet.com/oauth2/token
# Use token
curl -H "Authorization: Bearer <access_token>" \
  -H "X-Request-Id: $(uuidgen)" \
  https://api.sandbox.onelinkwallet.com/v1/transfers/trf_123
Token lifetime
~15 minutes (short-lived JWT access tokens; rotate proactively)
Scopes
transfers:create, transfers:read, webhooks:manage
mTLS (prod)
Recommended for client auth & webhook receivers
IP allow-list
Defense-in-depth only; not the primary gate
Key rotation
Register multiple credentials; support seamless rotation
Note: Legacy API keys are supported only for migration. New integrations must use OAuth 2.1 or mTLS.

Response & Webhook Signing

Every response and webhook includes a signature to guarantee integrity and defend against replay. Compute HMAC-SHA256 over ${timestamp}.${raw_body} using your shared secret. JSON bodies are canonicalized (RFC 8785 JCS).

X-OLW-Timestamp: 1735668000
X-OLW-Signature: sha256=1e5c7f...
  • Reject if timestamp skew > 300s, signature mismatch, or nonce replay.
  • Rotate secrets routinely; store in KMS/HSM; maintain key IDs (kid).
  • Alternate: JWS (RS256) with our JWKS at /.well-known/jwks.json.

Rate Limits & Idempotency

We publish RateLimit-* headers (RFC 9331). Use Idempotency-Key (UUIDv4 or 256-bit token) on all POSTs that create state.

Idempotency-Key: 8c9f2c18-5c3a-4b0f-9db7-8a4f7e4a7332
  • Duplicate key within 24h returns the original result (or 409 if first failed validation).
  • Retry policy: exponential backoff for 502/503/504 and network timeouts. Do not retry 4xx.
  • Headers: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset.

Error Model (RFC 7807)

{
  "type": "https://docs.onelinkwallet.com/errors/validation",
  "title": "Invalid request",
  "status": 400,
  "detail": "currency must be ISO 4217",
  "instance": "req_01HW...Q2",
  "errors": [{"path":"/amount/currency","code":"invalid_format"}]
}
ScenarioHTTPNotes
Validation error400Problem+JSON body; field-level errors[]
Unauthorized401Invalid/expired token
Forbidden403Missing scope or mTLS required
Not found404Unknown resource
Rate limit429With Retry-After seconds
Destination error502/503Upstream PSP/wallet issue
Timeout504Retry with backoff

Resources

Create Transfer

Create a transfer between two wallets. Validation occurs synchronously; processing may complete asynchronously (see webhooks).
POST /v1/transfers
Authorization: Bearer <token>
Idempotency-Key: <uuid>
Content-Type: application/json
{
  "amount": {"value": "2500", "currency": "EUR"},
  "source": {"walletId": "wal_5m4b7azr", "reference": "INV-90351"},
  "destination": {"walletId": "wal_dp2v6g0q"},
  "metadata": {"orderId": "o_12345", "notes": "Priority"},
  "callbackUrl": "https://merchant.example.com/webhooks/olw"
}
201 Created
{
  "transferId": "trf_0kz2cd8g",
  "status": "accepted",
  "decision": {"riskScore": 18, "action": "proceed", "reasons": ["geovelocity_ok","kyc_match"]},
  "createdAt": "2025-01-05T14:30:00Z"
}

Request details

FieldTypeRequiredDescription
amount.valuestringyesMinor units (e.g., cents). No decimals; max 12 digits.
amount.currencystringyesISO 4217 (e.g., EUR, USD, SAR).
source.walletIdstringyesWallet to debit (format wal_*).
source.referencestringnoFree-form reference, max 64 chars.
destination.walletIdstringyesWallet to credit.
metadata.*objectnoKey/value pairs (up to 20 keys; values ≤ 256 chars).
callbackUrlstringnoOverride default webhook URL for this transfer.

Get Transfer

GET /v1/transfers/{transferId}
{
  "transferId": "trf_0kz2cd8g",
  "status": "processed",
  "processedAt": "2025-01-05T14:31:07Z",
  "receipt": {"hash": "b7c1...", "ledgerRef": "L-9384021"},
  "decision": { "riskScore": 18, "action": "proceed" },
  "amount": {"value":"2500","currency":"EUR"},
  "source": {"walletId":"wal_5m4b7azr"},
  "destination": {"walletId":"wal_dp2v6g0q"},
  "metadata": {"orderId":"o_12345"}
}

List Transfers

GET /v1/transfers?limit=50&cursor=eyJwYWdlIjoyfQ&status=processed&createdFrom=2025-01-01T00:00:00Z
QueryTypeDescription
limitint (1–200)Default 50
cursorstringOpaque token from previous page
statusenumaccepted|processing|processed|failed|canceled
walletIdstringFilter by source/destination wallet
createdFrom/TodatetimeRFC 3339 UTC
sortenumcreatedAt_desc (default) or createdAt_asc
{
  "data": [ /* transfers */ ],
  "nextCursor": "eyJwYWdlIjozfQ"
}

Cancel Transfer

Attempts to cancel a transfer that has not yet reached an irreversible state.
POST /v1/transfers/{transferId}/cancel
Idempotency-Key: <uuid>
{
  "transferId": "trf_0kz2cd8g",
  "status": "canceled",
  "canceledAt": "2025-01-05T14:30:20Z"
}
  • Only allowed from accepted or early processing.
  • May return 409 Conflict if already processed or failed.

Schemas & Validation

Money

FieldTypeConstraints
valuestringMinor units; 1–12 digits; no sign/decimals
currencystringISO 4217 (uppercase)

WalletRef

FieldTypeConstraints
walletIdstringPattern ^wal_[a-z0-9]{8,32}$
referencestringMax 64 chars; printable ASCII

Transfer

FieldTypeNotes
transferIdstringServer-issued trf_*
statusenumaccepted|processing|processed|failed|canceled
decisionobjectRisk score, action, reasons
receiptobjectLedger/settlement references
createdAt/processedAtdatetimeRFC 3339 UTC

Common validation rules

  • Currency must match supported set for each tenant/region.
  • Amount must be ≥ 1 minor unit and within tenant limits.
  • Source ≠ Destination wallet.
  • Metadata total size ≤ 4 KB.

Status Lifecycle

  • accepted → basic validation passed; queued for processing.
  • processing → routing, risk checks, destination handshake.
  • processed → committed; receipt available.
  • failed → validation/routing error; see errors[].
  • canceled → canceled before irreversible step.
You’ll receive webhooks at each transition. Use the webhook as the source of truth; avoid hot-polling.

Observability & Correlation

  • Send a unique X-Request-Id per call; we echo it back and include it in webhooks.
  • W3C Trace Context supported via traceparent/tracestate.
  • Structured JSON logs; tamper-evident archival of audit trails.

Transport, TLS & CORS

  • TLS 1.3 preferred; 1.2 allowed with modern AEAD ciphers (GCM/ChaCha20-Poly1305).
  • HSTS enabled in production; no plaintext redirects for API hosts.
  • CORS: restricted allow-list; no wildcard origins; preflight required.
  • SSRF defenses on callback/webhook URLs; only https:// with resolvable public DNS.

Code Examples

Node (fetch)

import fetch from "node-fetch";
const BASE = "https://api.sandbox.onelinkwallet.com/v1";
async function createTransfer(token){
  const res = await fetch(BASE + "/transfers", {
    method:"POST",
    headers:{
      "Authorization": "Bearer " + token,
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID(),
      "X-Request-Id": crypto.randomUUID()
    },
    body: JSON.stringify({
      amount:{value:"2500",currency:"EUR"},
      source:{walletId:"wal_5m4b7azr"},
      destination:{walletId:"wal_dp2v6g0q"}
    })
  });
  if(!res.ok) throw new Error(await res.text());
  return res.json();
}

Python (requests)

import requests, uuid
BASE="https://api.sandbox.onelinkwallet.com/v1"
headers={"Authorization":"Bearer "+ACCESS_TOKEN,
         "Content-Type":"application/json",
         "Idempotency-Key":str(uuid.uuid4()),
         "X-Request-Id":str(uuid.uuid4())}
payload={"amount":{"value":"2500","currency":"EUR"},
         "source":{"walletId":"wal_5m4b7azr"},
         "destination":{"walletId":"wal_dp2v6g0q"}}
r=requests.post(f"{BASE}/transfers", json=payload, headers=headers, timeout=30)
r.raise_for_status()
print(r.json())

Webhooks

Configure with POST /v1/webhooks. Events: transfer.created, transfer.processed, transfer.failed. Retries: 0s, 30s, 2m, 10m, 1h (up to 15 attempts). Non-2xx responses trigger retries; 4xx are not retried except 429 (with backoff).

POST /v1/webhooks
{"url":"https://merchant.example.com/webhooks/olw","events":["transfer.processed"]}
POST /webhooks/olw
X-OLW-Event: transfer.processed
X-OLW-Delivery: evt_89k0w1
X-OLW-Timestamp: 1735668000
X-OLW-Signature: sha256=1e5c7f...
{"transferId":"trf_0kz2cd8g","status":"processed","processedAt":"2025-01-05T14:31:07Z"}
  • Verify X-OLW-Signature and timestamp (<= 300s skew) before acknowledging.
  • Support rotating secrets (key IDs) and maintain an event de-duplication store (at-least-once delivery).
  • Optional mTLS: present client cert; we validate against your registered CA chain.
Respond with 2xx within 10s. Use a dead-letter queue and alerting for repeated failures.

Health, Status & SLAs

  • GET /v1/health — liveness; unauthenticated 200 if healthy.
  • GET /v1/status — readiness; authenticated; returns upstream dependency status.
  • Retry budgets & circuit breaker guidance: exponential backoff, jitter; cap retries to avoid thundering herds.
  • Availability targets published on status page; maintenance windows announced in advance.

Environments & Headers

  • Sandbox is open after onboarding; production requires credentials approval.
  • Always send: Accept: application/json, User-Agent, X-Request-Id.
  • Observability: we forward your X-Request-Id to logs and webhooks.

Compliance & Privacy

  • GDPR/UK GDPR ready; DPA available. Data minimization required.
  • FIPS 140-3 crypto modules; keys in managed KMS/HSM; rotation policy enforced.
  • Log hygiene: never log tokens, secrets, or full PII; mask sensitive values.
  • Retention defaults: 180 days logs, 365 days transfer metadata (tenant configurable).
  • Data residency: stored in-region as contractually agreed; cross-border transfers based on appropriate safeguards.