HyperSwitch: The Open-Source Payments Orchestrator That Replaces Your PSP Stack
HyperSwitch: The Open-Source Payments Orchestrator That Replaces Your PSP Stack
You integrate Stripe. Then Adyen for Europe. Then Braintree for PayPal. Then a local acquirer for Brazil. Each has its own SDK, webhook format, retry logic, and fee structure. Your codebase grows if (provider === 'stripe') branches like kudzu.
This is the problem HyperSwitch solves.
What Actually Is HyperSwitch?
HyperSwitch is an open-source payments orchestration platform — 45.6k GitHub stars, Apache 2.0, written in Rust, battle-tested at 300M daily transactions and $1T+ annual TPV by Juspay (India's largest payments processor). It sits between your application and 120+ payment processors, providing a single API that handles routing, retries, vaulting, reconciliation, and cost observability.
Think of it as the Linux of payments: you integrate once, then plug in whichever processors you need.
The Architecture That Makes It Work
Router + Scheduler: Two Services, One Job
HyperSwitch runs as two stateless Rust binaries:
Router — The hot path. Handles every payment request: authentication, FRM checks, 3DS decisions, connector selection via the Euclid routing engine, credential resolution from the vault, and connector dispatch through the Connector Abstraction Layer (CAL).
Scheduler — Background jobs. Producer batches scheduled tasks (card cleanup, API key expiry notices) into Redis queues; Consumer executes them. Horizontal scaling is trivial — any instance can handle any request.
All state lives in PostgreSQL. Redis handles caching, distributed locks, and the scheduler queue.
The Payment State Machine: Intent vs Attempt
This is where most orchestrators get it wrong. HyperSwitch separates two concepts:
- payment_intent — The merchant's request. One per checkout session. Holds amount, currency, customer, capture method, setup_future_usage.
- payment_attempt — A single authorization try against a specific PSP. Multiple attempts per intent (retries). Each has its own connector, connector_transaction_id, error_code, error_details.
When a retry happens, a new payment_attempt is created. The merchant-facing payment_id never changes — only the internal suffix (pay_abc_1, pay_abc_2) sent to the PSP changes.
Euclid: Routing That Isn't Just Round-Robin
The Euclid routing engine evaluates strategies in strict priority order:
1. Rule-based — Merchant-defined conditions: payment_method_type = visa AND country = DE AND amount > 10000 → use Adyen
2. Volume-based — Percentage split across eligible connectors
3. Cost-based — Select lowest MDR (merchant discount rate) among eligible
4. Default fallback — Ordered priority list from merchant config
After selection, eligibility analysis runs: Does this connector support the payment method + currency? Is it enabled for this profile? If any check fails, Euclid moves to the next candidate. Exhaust the list → payment fails without a single PSP call.
GSM: Normalizing 120+ Error Languages
Every PSP returns proprietary error codes. Stripe's card_declined ≠ Adyen's REFUSED ≠ Braintree's 2001. GSM (Global Status Monitoring) maps (connector, error_code, error_message) → standardized unified_code + retry flags:
| Flag | Meaning |
|------|---------|
| retriable | Any retry eligible |
| step_up_possible | 3DS challenge could succeed |
| clear_pan_possible | Network token failed, try raw PAN |
| alternate_network_possible | Switch debit network (Visa → Maestro) |
The retry decision flow is deterministic: GSM lookup → check flags in order (Step-Up → Clear PAN → Network → Cascading) → do_retry() creates new payment_attempt → re-enter Euclid.
The Vault: PCI Compliance Without the Pain
The Card Vault is a separate microservice. Sensitive data never touches your Router — or your application if you use the client-side SDK.
Tokenization flow (client-side): SDK → Vault /session_tokens → JWE-encrypted request → Vault decrypts, stores PAN, returns payment_method_id → Your backend sends payment_method_id to Router → Router calls Vault Locker API → Vault returns credential (PAN, network token, or proxy reference) → Router dispatches to PSP.
Key management: Custodian-based. Master key derived from multiple custodian shares (Shamir's Secret Sharing). Vault only activates when quorum of custodians unlock it. Zero data egress in self-hosted mode.
Reconciliation: Double-Entry for Payments
Merchants routing across PSPs get separate settlement reports — different formats, different schedules. HyperSwitch's reconciliation engine applies double-entry bookkeeping: every payment event generates balanced debit/credit ledger entries.
- 2-way: Hyperswitch ledger ↔ PSP settlement report
- 3-way: Hyperswitch ledger ↔ PSP settlement ↔ OMS order data
Auto-fetch pipelines pull reports from PSPs (SFTP, API, email), normalize via connector-specific rules, match on connector_transaction_id + amount + date with configurable tolerance windows, age unmatched entries, and surface exceptions in the Control Center.
What It Replaces
| Paid Stack | HyperSwitch Module |
|------------|-------------------|
| Stripe + Adyen + Braintree integrations | Single API, 120+ connectors |
| Custom routing logic | Euclid engine (rules, volume, cost) |
| Manual retry code | GSM-driven smart retries (4 strategies) |
| PCI vault (TokenEx, VGS) | Built-in Card Vault + Network Tokens |
| Spreadsheet reconciliation | Automated 2/3-way engine |
| DataDog/New Relic for payments | Cost Observability dashboards |
Self-Hosting: What You Actually Need
# docker-compose.yml (simplified)
services:
router:
image: juspaydotin/hyperswitch-router:latest
environment:
- DATABASE_URL=postgres://...
- REDIS_URL=redis://...
- LOCKER_URL=http://vault:8080
ports: ["8080:8080"]
vault:
image: juspaydotin/hyperswitch-locker:latest
environment:
- DATABASE_URL=postgres://...
- CUSTODIAN_KEYS=...
ports: ["8081:8081"]
scheduler:
image: juspaydotin/hyperswitch-scheduler:latest
environment:
- DATABASE_URL=postgres://...
- REDIS_URL=redis://...
postgres: ...
redis: ...
Deploy via Docker Compose, Kubernetes (Helm), or AWS CloudFormation. Control Center (React dashboard) runs separately for routing config, analytics, and exception management.
The Trade-offs
Not a drop-in Stripe replacement. You operate infrastructure. PCI-SSS validation requires the Encryption Service + custodian ceremony. Connector coverage is broad but not infinite — niche local methods may need custom connectors (template provided). The learning curve is real: routing rules, GSM mappings, vault activation.
But you get: Zero vendor lock-in. Full visibility into every hop. Routing logic in config, not code. No per-transaction markup. Apache 2.0 — fork it, extend it, audit it.
When to Adopt
- Multiple PSPs already, or planning to add them
- Team capable of running PostgreSQL + Redis + Rust services
- Need routing/retries/vault/reconciliation as configurable infrastructure
- Willing to invest in ops for long-term control
Start Here
# Quick local test
git clone https://github.com/juspay/hyperswitch
cd hyperswitch/docker
docker compose up -d
# Router at localhost:8080, Control Center at localhost:3000
Read the architecture deep-dive for the Router internals, Vault architecture for tokenization internals, and Reconciliation architecture for the ledger model.
---
The payments stack you'd build if you had 12 years and 300M daily transactions to refine it. Now you don't have to.
Mentions
HyperSwitch: The Open-Source Payments Orchestrator That Replaces Your PSP Stack HyperSwitch: The Open-Source Payments Orchestrator That Replaces Your PSP Stack You integrate Stripe. Then Adyen for Europe. Then Braintree for PayPal. Then a local acquirer for Brazil. Each has its own SDK, webhook format, retry logic, and fee structure. Your codebase grows if (provider === 'stripe') branches like kudzu. This is the problem HyperSwitch solves. What Actually Is HyperSwitch? HyperSwitch is an open-source payments orchestration platform — 45.6k GitHub stars, Apache 2.0, written in Rust, battle-tested at 300M daily transactions and $1T+ annual TPV by Juspay (India's largest payments processor). It sits between your application and 120+ payment processors, providing a single API that handles routing, retries, vaulting, reconciliation, and cost observability. Think of it as the Linux of payments: you integrate once, then plug in whichever processors you need. The Architecture That Makes It Work Router + Scheduler: Two Services, One Job HyperSwitch runs as two stateless Rust binaries: Router — The hot path. Handles every payment request: authentication, FRM checks, 3DS decisions, connector selection via the Euclid routing engine, credential resolution from the vault, and connector dispatch through the Connector Abstraction Layer (CAL). Scheduler — Background jobs. Producer batches scheduled tasks (card cleanup, API key expiry notices) into Redis queues; Consumer executes them. Horizontal scaling is trivial — any instance can handle any request. All state lives in PostgreSQL. Redis handles caching, distributed locks, and the scheduler queue. The Payment State Machine: Intent vs Attempt This is where most orchestrators get it wrong. HyperSwitch separates two concepts: - payment_intent — The merchant's request. One per checkout session. Holds amount, currency, customer, capture method, setup_future_usage. - payment_attempt — A single authorization try against a specific PSP. Multiple attempts per intent (retries). Each has its own connector, connector_transaction_id, error_code, error_details. When a retry happens, a new payment_attempt is created. The merchant-facing payment_id never changes — only the internal suffix (pay_abc_1, pay_abc_2) sent to the PSP changes. Euclid: Routing That Isn't Just Round-Robin The Euclid routing engine evaluates strategies in strict priority order: 1. Rule-based — Merchant-defined conditions: payment_method_type = visa AND country = DE AND amount > 10000 → use Adyen 2. Volume-based — Percentage split across eligible connectors 3. Cost-based — Select lowest MDR (merchant discount rate) among eligible 4. Default fallback — Ordered priority list from merchant config After selection, eligibility analysis runs: Does this connector support the payment method + currency? Is it enabled for this profile? If any check fails, Euclid moves to the next candidate. Exhaust the list → payment fails without a single PSP call. GSM: Normalizing 120+ Error Languages Every PSP returns proprietary error codes. Stripe's card_declined ≠ Adyen's REFUSED ≠ Braintree's 2001. GSM (Global Status Monitoring) maps (connector, error_code, error_message) → standardized unified_code + retry flags: | Flag | Meaning | |------|---------| | retriable | Any retry eligible | | step_up_possible | 3DS challenge could succeed | | clear_pan_possible | Network token failed, try raw PAN | | alternate_network_possible | Switch debit network (Visa → Maestro) | The retry decision flow is deterministic: GSM lookup → check flags in order (Step-Up → Clear PAN → Network → Cascading) → do_retry() creates new payment_attempt → re-enter Euclid. The Vault: PCI Compliance Without the Pain The Card Vault is a separate microservice. Sensitive data never touches your Router — or your application if you use the client-side SDK. Tokenization flow (client-side): SDK → Vault /session_tokens → JWE-encrypted request → Vault decrypts, stores PAN, returns payment_method_id → Your backend sends payment_method_id to Router → Router calls Vault Locker API → Vault returns credential (PAN, network token, or proxy reference) → Router dispatches to PSP. Key management: Custodian-based. Master key derived from multiple custodian shares (Shamir's Secret Sharing). Vault only activates when quorum of custodians unlock it. Zero data egress in self-hosted mode. Reconciliation: Double-Entry for Payments Merchants routing across PSPs get separate settlement reports — different formats, different schedules. HyperSwitch's reconciliation engine applies double-entry bookkeeping: every payment event generates balanced debit/credit ledger entries. - 2-way: Hyperswitch ledger ↔ PSP settlement report - 3-way: Hyperswitch ledger ↔ PSP settlement ↔ OMS order data Auto-fetch pipelines pull reports from PSPs (SFTP, API, email), normalize via connector-specific rules, match on connector_transaction_id + amount + date with configurable tolerance windows, age unmatched entries, and surface exceptions in the Control Center. What It Replaces | Paid Stack | HyperSwitch Module | |------------|-------------------| | Stripe + Adyen + Braintree integrations | Single API, 120+ connectors | | Custom routing logic | Euclid engine (rules, volume, cost) | | Manual retry code | GSM-driven smart retries (4 strategies) | | PCI vault (TokenEx, VGS) | Built-in Card Vault + Network Tokens | | Spreadsheet reconciliation | Automated 2/3-way engine | | DataDog/New Relic for payments | Cost Observability dashboards | Self-Hosting: What You Actually Need # docker-compose.yml (simplified) services: router: image: juspaydotin/hyperswitch-router:latest environment: - DATABASE_URL=postgres://... - REDIS_URL=redis://... - LOCKER_URL=http://vault:8080 ports: ["8080:8080"] vault: image: juspaydotin/hyperswitch-locker:latest environment: - DATABASE_URL=postgres://... - CUSTODIAN_KEYS=... ports: ["8081:8081"] scheduler: image: juspaydotin/hyperswitch-scheduler:latest environment: - DATABASE_URL=postgres://... - REDIS_URL=redis://... postgres: ... redis: ... Deploy via Docker Compose, Kubernetes (Helm), or AWS CloudFormation. Control Center (React dashboard) runs separately for routing config, analytics, and exception management. The Trade-offs Not a drop-in Stripe replacement. You operate infrastructure. PCI-SSS validation requires the Encryption Service + custodian ceremony. Connector coverage is broad but not infinite — niche local methods may need custom connectors (template provided). The learning curve is real: routing rules, GSM mappings, vault activation. But you get: Zero vendor lock-in. Full visibility into every hop. Routing logic in config, not code. No per-transaction markup. Apache 2.0 — fork it, extend it, audit it. When to Adopt - Multiple PSPs already, or planning to add them - Team capable of running PostgreSQL + Redis + Rust services - Need routing/retries/vault/reconciliation as configurable infrastructure - Willing to invest in ops for long-term control Start Here # Quick local test git clone https://github.com/juspay/hyperswitch cd hyperswitch/docker docker compose up -d # Router at localhost:8080, Control Center at localhost:3000 Read the architecture deep-dive for the Router internals, Vault architecture for tokenization internals, and Reconciliation architecture for the ledger model. --- The payments stack you'd build if you had 12 years and 300M daily transactions to refine it. Now you don't have to.
😤 接支付通道一家家对接,手续费还吃一层。Hyperswitch 4.5 万星开源支付平台,几十家通道收进一个接口,换通道不改业务代码,还能自己部署。 juspay/hyperswitch
@martin_valchev_ It's built with NextJs & Typescript, powered by juspay/streamgres
@Joi2James try out: juspay/streamgres This is powering xyne.ai
We also open-sourced Streamgres during the session. The code is available here: juspay/streamgres
Cool new sync engine, just open sourced today juspay/streamgres