A self-hosted payment gateway for SEPA Instant Transfers with real-time WebSocket notifications. Built for businesses accepting online and offline EUR payments across Europe.
- π¦ SEPA Payments - Accept SEPA Instant Transfers via bank integrations (Qonto, custom providers)
- π Self-Hosted - Full data sovereignty with your own database and infrastructure
- β‘ Real-time - WebSocket notifications for instant payment confirmations
- π’ Multi-tenant - Organization-based access with API key management
- π± Embeddable SDK - Lightweight JavaScript widget for merchant integration
- π Secure - HMAC webhook verification, rate limiting, and structured logging
- π§ͺ Test Mode - Built-in test bank simulator for development
- Node.js 22+
- pnpm 10+
- Docker & Docker Compose
pnpm installCreate .env files for each app based on your configuration. Key variables include:
DATABASE_USER- PostgreSQL userDATABASE_PASSWORD- PostgreSQL passwordDATABASE_HOST- PostgreSQL hostDATABASE_PORT- PostgreSQL portDATABASE_NAME- PostgreSQL databaseREDIS_URL- Redis URL for pub/subAUTH_SECRET- Better Auth secret keyNEXT_PUBLIC_APP_URL- Public app URL
# Start PostgreSQL and Redis
docker compose up -d# Generate Prisma client
pnpm db:generate
# Push schema to database
pnpm db:push# Start all services (Next.js + WSS + Demo + Test Bank)
pnpm dev
# Or start individually:
pnpm dev:next # Next.js dashboard + API (port 3000)
pnpm dev:wss # WebSocket server
pnpm dev:demo # Demo merchant site (port 3002)
pnpm dev:test-bank # Mock bank simulator (port 3003)The dashboard will be available at http://localhost:3000
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Infrastructure β
βββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββ¬ββββββββββ€
β PostgreSQL β Redis β β
β (Database) β (Pub/Sub) β β
βββββββββββββββ΄βββββββββββββββ¬βββββββββββββββββββββββββββ΄ββββββββββ
β
ββββββββββββββββββββββΌβββββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββ βββββββββββββββββ βββββββββββββββββ
β Next.js β β WebSocket β β Client β
β Dashboard ββββββ Server ββββββ SDK β
β + API β β (Socket.io) β β (GetBlitz) β
βββββββββββββββββ βββββββββββββββββ βββββββββββββββββ
β β² β
β β β
βΌ β βΌ
βββββββββββββββββ β βββββββββββββββββ
β Bank Providerββββββββββββββ β Merchant β
β Webhook β β Website β
βββββββββββββββββ βββββββββββββββββ
getblitz/
βββ apps/
β βββ web/ # Dashboard + API routes (Next.js 16)
β βββ wss/ # WebSocket server (Socket.io)
β βββ demo/ # Demo merchant site
β βββ test-bank/ # Mock bank simulator for development
βββ packages/
β βββ api/ # tRPC routers, services, repositories (DI pattern)
β βββ auth/ # Better Auth configuration
β βββ bank-providers/ # Bank provider integrations (Qonto, test-bank)
β βββ database/ # Prisma schema and client
β βββ redis/ # Redis client and pub/sub
β βββ shared-types/ # TypeScript interfaces
β βββ ui/ # Shared UI components (shadcn/ui)
β βββ validators/ # Zod schemas
β βββ websocket/ # WebSocket utilities
β βββ getblitz-client/ # Embeddable payment SDK
βββ tooling/ # ESLint, Prettier, TypeScript, Vitest configs
POST /api/v1/challenge
Authorization: Bearer <API_KEY>
Content-Type: application/json
{
"amount": 500, # Amount in cents (β¬5.00)
"currency": "EUR", # EUR only
"bankAccountId": "uuid" # Optional: specific bank account
}Response:
{
"sessionId": "uuid",
"referenceId": "GB-A9F3B2C1",
"paymentUrl": "https://pay.example.com/pay/uuid",
"expiresAt": "2024-01-01T12:15:00.000Z"
}The gateway sends webhooks to merchants for payment events:
| Event | Description |
|---|---|
payment.success |
Payment completed (full amount received) |
payment.partial |
Partial payment received (for split payments) |
payment.failed |
Payment failed |
payment.expired |
Payment session expired |
All webhooks include amountPaidCents showing current progress toward the total amountCents.
π See docs/webhooks.md for payload schema and signature verification.
<script src="https://cdn.yourdomain.com/getblitz.js"></script>
<script>
const payment = new GetBlitz({
sessionId: "sess_123",
apiUrl: "https://pay.yourdomain.com",
wssUrl: "wss://wss.yourdomain.com",
});
payment.mount("#payment-container");
payment
.on("onSuccess", (token) => {
console.log("Payment successful:", token);
})
.on("onError", (error) => {
console.error("Payment failed:", error);
})
.on("onExpired", () => {
console.log("Payment session expired");
});
</script>GetBlitz supports multiple bank providers through a pluggable adapter system.
π See docs/banks/ for detailed setup guides.
| Provider | Auth Type | Description | Setup Guide |
|---|---|---|---|
| Qonto | OAuth2 | Business banking for SMEs | View |
| Revolut | Certificate | Business banking for SMEs | View |
| Test Bank | None | Mock provider for development | View |
Note: Test Bank is automatically hidden in production environments.
Bank providers implement a standard interface in packages/bank-providers:
interface BankProvider {
id: string;
displayName: string;
authType: "oauth2" | "api_key" | "certificate" | "none";
isTestProvider: boolean;
getSetupGuide(): string | null;
getAuthUrl(params: AuthParams): string;
exchangeCode(params: CodeParams): Promise<BankCredentials>;
listAccounts(params: AccountParams): Promise<Account[]>;
verifyAndParseWebhook(params: WebhookParams): Promise<WebhookResult>;
createWebhook(params: WebhookCreateParams): Promise<WebhookConfig>;
}| Variable | Description | Required |
|---|---|---|
DATABASE_USER |
PostgreSQL user | β |
DATABASE_PASSWORD |
PostgreSQL password | β |
DATABASE_HOST |
PostgreSQL host | β |
DATABASE_PORT |
PostgreSQL port | β |
DATABASE_NAME |
PostgreSQL database | β |
REDIS_URL |
Redis URL for pub/sub | β |
AUTH_SECRET |
Better Auth secret key | β |
NEXT_PUBLIC_APP_URL |
Public app URL | β |
WSS_URL |
WebSocket server URL | β |
CRON_SECRET |
Cron job auth secret |
Provider-specific variables (e.g., Qonto OAuth credentials) should be configured per your bank integration.
# Run all tests
pnpm test
# Run tests for a specific package
pnpm -F @getblitz/api testpnpm db:push # Push schema to database
pnpm db:migrate # Run migrations
pnpm db:studio # Open Prisma Studio
pnpm db:generate # Generate Prisma clientpnpm lint # Run ESLint
pnpm lint:fix # Fix lint issues
pnpm format # Check formatting
pnpm format:fix # Fix formatting
pnpm typecheck # Run TypeScript checksSee DEPLOYMENT.md for detailed instructions and environment configuration.
# Build and run with Docker Compose
docker compose -f compose.yml up -d- Connect your repository to Vercel
- Set the root directory to
apps/web - Add environment variables
- Deploy
Set up a cron job to expire pending sessions:
# Every minute
* * * * * curl -H "Authorization: Bearer $CRON_SECRET" https://pay.yourdomain.com/api/cron/expire-sessionsOr use Vercel Cron by adding to apps/web/vercel.json:
{
"crons": [
{
"path": "/api/cron/expire-sessions",
"schedule": "* * * * *"
}
]
}- Merchant creates a payment challenge via API
- Customer is redirected to payment page
- Customer scans EPC-QR code with their bank app
- Customer completes SEPA Instant Transfer
- Bank Webhook sends payment confirmation
- API matches payment by reference ID
- API updates payment status to PAID
- Redis publishes event for real-time notification
- WSS forwards event to connected clients
- SDK triggers onSuccess callback
- API Keys: Secure, rotatable keys per organization
- Webhook Verification: HMAC-SHA256 signature validation
- Rate Limiting: Configurable limits via Redis
- CORS: Strict origin validation on WebSocket connections
- Database: ACID transactions for payment state updates
MIT