Skip to main content

Switching Service - Integration Guide

Audience: Engineers integrating a client application with the Flex switching service. Base URL: https://<switch-host>/api (all paths below omit this prefix)

Table of Contents

  1. What Switch Is
  2. Core Concepts
  3. How the switch works end to end
  4. Quick-Start Checklist
  5. Step 1 - Register Your Application ← includes appHandle
  6. Step 2 - Authenticate (Get a Token)
  7. Step 3 - Create Subjects and Tags
  8. Step 4 - Initiate a Payment
  9. Step 5 - Receive and Handle Webhooks
  10. Step 6 - Accept or Reject a Transaction
  11. Step 7 - Monitor Webhooks and Delivery
  12. Step 8 - Wallet and Ledger
  13. Step 9 - Settlement
  14. Payload Encryption (JWE)
  15. Webhook Signature Verification
  16. Idempotency
  17. Rate Limiting
  18. Cross-App Tag Claims and Consent
  19. Namespaced Tags - Onboarding Existing Tag Systems ← new
  20. Tag Federation
  21. Error Handling
  22. API Reference Summary
  23. Integration Flows - Sequence Diagrams

1. What the switching service is

The Flex switching service routes payments between participating applications. It does not hold user balances as a primary product model. Instead, it uses paytags to identify destination accounts across applications. Each receiving application is responsible for honoring the transaction for its own users. Completed transactions are recorded in application wallets and settled at the end of the day. Your application communicates with the switch exclusively via HTTPS REST APIs. All side effects such as webhook dispatch and audit logging happen asynchronously via Kafka so API calls remain fast.

2. Core Concepts

Application

The top-level entity in Switch - it represents your service. Each application gets:
  • A clientId and clientSecret for authentication
  • A webhookSecret for verifying inbound webhook signatures
  • An EC P-256 keypair for payload encryption
  • One escrow wallet for net monetary position tracking

Subject

A user identity within your application. A subject maps your internal user ID (externalId) to the Switch system. One subject can own many tags.

PayTag

A portable payment identifier (for example alice, shop-main) scoped to the creating application. Each application has a short, unique appHandle slug (for example walletapp). The paytag’s globally unique identifier is the qualified address: localTag@appHandle (for example alice@walletapp). This means two different applications can each have a user named alice without any conflict - they become alice@walletapp and alice@shopapp, clearly distinct identities. Tag rules:
  • localTag part: 3–64 characters, lowercase alphanumeric only
  • appHandle part: 3–30 characters, lowercase alphanumeric + hyphens, not starting or ending with a hyphen; set at registration time and immutable
  • Tags can be resolved publicly using either the bare local name (alice) or the qualified address (alice@walletapp)
  • A bare name lookup that matches tags in more than one application returns 409 Conflict - use the qualified address in that case
  • Can receive payments from any other tag on any application

Transaction

A switch-routed payment from one paytag to another. Transactions go through a two-step lifecycle: the sending application initiates, and the receiving application explicitly accepts or rejects.

Wallet

The switch maintains one ApplicationWallet per application. It tracks the cumulative credits and debits from all completed transactions. This is an escrow/settlement wallet, not an end-user wallet. Per-user balances remain the application’s responsibility.

Webhook

An outbound HTTP POST from the switch to your application notifying you of transaction events. There are two distinct notifications per transaction:
  • RECEIVER webhook — sent to the receiving application’s transactionWebhookUrl when a transaction is addressed to one of its paytags
  • SENDER callback — sent to the callbackUrl provided by the sending application when the transaction outcome is determined

3. How the switch works end to end


4. Quick-Start Checklist

  • Register your application (POST /v1/applications) - save clientSecret, webhookSecret, and key material; they are shown only once
  • Authenticate (POST /v1/auth/token) - obtain a Bearer JWT
  • Provision wallet (POST /v1/wallet) - idempotent, safe to call on every startup
  • Create subjects for your users (POST /v1/subjects)
  • Create tags for your users (POST /v1/tags)
  • Expose a webhook endpoint at your transactionWebhookUrl to receive TRANSACTION_INITIATED events
  • Verify webhook signatures using X-Switch-Signature on every inbound webhook
  • Respond with 2xx within 10 seconds on webhook delivery; Switch retries 3 times
  • Set up a callback endpoint for TRANSACTION_COMPLETED / TRANSACTION_REJECTED / TRANSACTION_EXPIRED notifications

5. Step 1 - Register Your Application

Registration is public (no auth required). Each application registration creates an independent tenant.

Request

Response

⚠️ Store immediately and securely:
  • clientSecret - used to obtain Bearer tokens; never shown again
  • webhookSecret - used to verify X-Switch-Signature on every inbound webhook; never shown again
  • appPrivateJwk - your application’s private EC key (contains d); required to decrypt JWE-encrypted webhook payloads; never shown again
  • appHandle - returned in the response and in GET /v1/applications/{id}; it is the namespace for all your tags (e.g. alice@walletapp). Immutable.
Lose any of these and you must rotate (use key/webhook secret rotation endpoints, or deactivate + re-register).

6. Step 2 - Authenticate (Get a Token)

All protected endpoints require a Bearer JWT. Tokens expire after 1 hour (configurable via SWITCH_APP_TOKEN_TTL).

Request (form-encoded)

Request (JSON body)

Response

Using the token

Add the JWT to every subsequent request:
Rate limit: 10 requests / minute per IP. Implement token caching - refresh proactively before expiry rather than waiting for a 401.

Token refresh pattern


7. Step 3 - Create Subjects and Tags

7.1 Create a Subject

A subject represents one of your users inside Switch. Create a subject for every user who needs a tag.
Save the returned id (Switch subject UUID) - you need it to create a tag.

7.2 Create a Tag

Switch automatically derives the qualified address by appending your application’s appHandle: alice → alice@walletapp.
The qualifiedAddress (alice@walletapp) is the canonical cross-platform identifier for this tag. Share it with other applications so they can address payments to it unambiguously.

7.3 Resolve Any Tag (Public, Cached)

Any application can look up any tag - no auth required. Results are cached for 30 seconds. You can resolve by bare local name or by qualified address:
If alice exists on more than one application, the bare-string lookup returns 409 Conflict with the list of qualified addresses. Use the qualified address to avoid ambiguity.
Use this before initiating a payment to confirm the destination tag exists and is ACTIVE.

8. Step 4 - Initiate a Payment

The sending application calls this endpoint to begin a cross-application payment.
Tag addressing: Both senderTag and receiverTag accept either a bare local name (alice) or a qualified address (alice@walletapp). Use the qualified address whenever possible - bare names that exist on more than one application return a 409 Conflict asking you to specify the qualified address.

Response

Transaction responses always return qualified addresses in senderTag and receiverTag (e.g. alice@walletapp, bob@shopapp).

What happens next

  1. Switch persists the transaction in AWAITING_ACCEPTANCE state
  2. Switch persists a TransactionWebhook record (direction=RECEIVER, status=PENDING)
  3. Switch publishes a WebhookDispatchEvent to the switch.webhooks Kafka topic
  4. The WebhookDispatchConsumer POSTs a TRANSACTION_INITIATED notification to the receiver app’s transactionWebhookUrl (asynchronously)
  5. The transaction auto-expires after 24 hours if not accepted

9. Step 5 - Receive and Handle Webhooks

Switch POSTs notifications to your transactionWebhookUrl. You must:
  1. Expose a publicly reachable HTTPS endpoint
  2. Verify the X-Switch-Signature header (see §15)
  3. Return 2xx within 10 seconds; Switch retries up to 3 times (after 1 s and 2 s back-off)

9.1 RECEIVER Webhook - TRANSACTION_INITIATED

Sent to the receiving application when a new payment is addressed to one of its tags.
What to do when you receive this:
  1. Verify the signature (see §15)
  2. Parse the reference - this is the key you’ll use to accept or reject
  3. Apply your own business logic (check funds, validate the order, etc.)
  4. Call POST /v1/transactions/{reference}/accept or .../reject within 24 hours
  5. Return 200 OK immediately - do your business logic asynchronously if needed

9.2 SENDER Callback - Outcome Notification

Sent to the sending application’s transaction-specific callbackUrl when provided; otherwise Switch falls back to the sending application’s registered transactionWebhookUrl after the transaction outcome is determined.

9.3 Webhook Endpoint Implementation Checklist


10. Step 6 - Accept or Reject a Transaction

Only the receiving application can accept or reject. Switch enforces this - calling accept/reject with the wrong token returns 403.

Accept

Call POST /v1/transactions/{reference}/accept with the receiver application’s token. No request body is required. On success:
  • Sender’s wallet is debited by amount
  • Receiver’s wallet is credited by amount
  • Two immutable LedgerEntry rows are written
  • Transaction status moves to COMPLETED
  • Sender’s callbackUrl receives TRANSACTION_COMPLETED (or the sender app’s transactionWebhookUrl if no transaction-specific callback was supplied)
The response confirms the transaction was accepted and includes the transaction reference, status, and acceptedAt timestamp.

Reject

Call POST /v1/transactions/{reference}/reject with the receiver application’s token. You can include an optional reason such as Insufficient items in stock. The reason field is optional (max 500 chars). On success:
  • No ledger movement
  • Transaction status moves to REJECTED
  • Sender’s callbackUrl receives TRANSACTION_REJECTED (or the sender app’s transactionWebhookUrl if no transaction-specific callback was supplied)

Error responses for accept/reject


11. Step 7 - Monitor Webhooks and Delivery

Check delivery status for a transaction

Returns up to two records - one RECEIVER and one SENDER:

Webhook delivery statuses

Find all failed webhooks for your app

Use this for operational monitoring - set up an alert if this list is non-empty.

12. Step 8 - Wallet and Ledger

Provision wallet (call on startup)

Idempotent - safe to call on every application start.

Get current balance

balance = totalCredited − totalDebited. A positive balance means you have net received more than you sent. A negative balance means you have net sent more than you received.

Get ledger statement (paginated)


13. Step 9 - Settlement

Switch automatically runs an end-of-day settlement job at 23:59 UTC every day. It aggregates all completed transactions for the day and produces per-application net positions.

Get today’s settlement (after 23:59 UTC)

Manual trigger (operational use)

Idempotent - if the date is already settled, returns the existing record.

14. Payload Encryption (JWE)

When encryptionEnabled=true for your application, all request bodies must be JWE-encrypted and all webhook payloads you receive will be JWE-encrypted. This provides end-to-end payload confidentiality on top of TLS.

Algorithm

14.1 Encrypting Requests Sent TO Switch

Fetch the Switch public key (no auth required):
Example (Java with Nimbus JOSE+JWT):
Example (Node.js with jose library):
Example (Python with joserfc):

14.2 Decrypting Webhooks Received FROM Switch

When Content-Encryption: JWE is present on an inbound webhook, the body is a JWE compact string encrypted with your application’s EC public key. Decrypt it with your private key (appPrivateJwk) received once at registration/rotation.

14.3 Key Rotation

Rotate your keypair periodically or after a key compromise:
Returns a new appPublicKeyJwk and appPrivateJwk - store the private JWK immediately, it is shown only once. After rotation:
  • Switch encrypts all subsequent webhooks with the new public key
  • Requests encrypted with the old public key will be rejected
  • Allow a brief overlap window for in-flight messages before destroying the old private key

14.4 Webhook Secret Rotation

If your application loses its webhook secret (or suspects compromise), rotate it without re-registering:
Returns a new webhookSecret - store it immediately, it is shown only once. After rotation:
  • Switch signs all subsequent webhooks with the new secret
  • Verification with the old secret fails immediately

15. Webhook Signature Verification

Every outbound webhook POST from Switch includes two headers:

Signature algorithm

Where webhookSecret is the raw (unhashed) secret returned at registration. It can also be rotated via POST /v1/applications/webhook-secret/rotate if lost or compromised.

Verification (pseudo-code)

Implementation examples

Java:
Node.js:
Python:

16. Idempotency

POST /v1/transactions supports the Idempotency-Key request header. Supplying the same key within 24 hours returns the original response without creating a duplicate transaction.
Best practices:
  • Use UUID v4 as idempotency keys
  • Generate the key before the first attempt and store it alongside your order/payment record
  • Re-use the same key on all retries of the same logical payment
  • Do not re-use keys across different payments

17. Rate Limiting

When the limit is exceeded, Switch returns:
Retry-After - number of seconds until the bucket refills. Read this value and wait at least that long before retrying.
X-RateLimit-Remaining - tokens remaining in the current window; 0 when you are throttled.
Handling 429 in your integration:

If your application needs to associate one of your users with a tag they own on another application (e.g. bob on ShopApp), you can request a claim through the consent flow.

18.1 Request a Claim

Switch sends a time-limited consent notification to the tag owner. The consent token URL is:
This is the URL you can present to the tag owner (e.g. via email or in-app notification) so they can approve or deny the claim.

18.3 After Approval

On approval, Switch returns an attestation JWT:
Store this token - it proves your application has an approved link to that tag. You can verify it with the Switch public key.

19. Namespaced Tags - Onboarding Existing Tag Systems

The problem

Many applications that integrate Switch already run their own tag / username system. When they onboard their users onto Switch, tag collisions are inevitable: WalletApp may already have a user alice, and so does ShopApp - completely different people. A single global UNIQUE(tag_string) constraint would block one application from onboarding. Switch solves this with qualified addressing.

How it works

Every application registers with a short, unique appHandle slug (e.g. walletapp). Every tag automatically derives a qualified address localTag@appHandle: These are different identities sharing a local name - Switch tracks them separately with no constraint conflict.

What stays the same for the host app

Your application continues to use its own tag system internally. Switch only requires that you onboard the tags you want to be discoverable cross-platform:
  1. Register on Switch with your appHandle
  2. Create a Subject for each user: POST /v1/subjects
  3. Create a Tag for each user you want discoverable: POST /v1/tags with {"tag": "alice", "subjectId": "..."}
  4. Switch records alice@walletapp as the globally unique qualified address
  5. Other applications discover your users via GET /v1/tags/alice@walletapp
  6. Payments are routed to alice@walletapp - your internal tag system is unaffected
Tags that are not onboarded are simply not discoverable via Switch. This is intentional - you control which users participate in the cross-platform network.

Address resolution behaviour

Example: sending a payment to a user on another app

The second form works only when alice is unique on the platform and bob is unique on the platform. If either is ambiguous, Switch returns 409 and lists the qualified addresses to use.

appHandle rules


20. Tag Federation

Federation links a tag to an identity on an external identity provider (e.g. “alice on WalletApp is the same person as alice@github.com”). This is for advanced cross-platform identity scenarios.
Switch issues a challenge; your application must verify ownership on the external provider, then call:

21. Error Handling

All Switch responses use a consistent ApiResponse<T> envelope:
errorCode is the stable machine-readable identifier (for example SWITCH-AUTH-1002). Client applications should branch handling logic by errorCode, not by free-text message.

Complete error code catalog

All Switch errors are returned with a stable error code in the errorCode field. Client applications should parse and branch on this code rather than free-text messages, enabling reliable structured error handling.

Authentication & Authorization (1000–1999)

Validation Errors (2000–2999)

Resource Not Found (3000–3999)

Resource Conflicts (4000–4999)

Invalid State / Business Logic (5000–5999)

Rate Limiting (6000–6999)

System / Internal Errors (9000–9999)

Error Code Categories

Total: 41 documented error codes – all error responses include errorCode and message.

HTTP status codes

Validation error example


22. API Reference Summary

Base path for all endpoints: /api/v1
All protected endpoints require: Authorization: Bearer <token>

Applications & Authentication

Encryption Key Management

Subjects

Tags

Transactions

Wallet & Ledger

Settlement

Webhooks

Claims

Federation

Actuator


23. Integration Flows - Sequence Diagrams

Flow A: Two-App Payment (Happy Path)

Flow B: Transaction Rejected

Flow C: Transaction Expired

Flow D: Encrypted Payment