Keycloak × Stripe: Building Invisible Marketplace Onboarding with Event-Driven Architecture
• 5 min read• By Stephane Segning Lambou
Blog
Discover how to build a seamless marketplace onboarding experience by integrating Keycloak and Stripe Connect using event-driven reactive Kotlin and AMQP.
Marketplace onboarding is often a fragmented experience: users create an account, complete additional registration steps, and manually connect payment capabilities. Every extra step increases friction.
In this article, we show how we integrated Keycloak and Stripe Connect to automate large parts of the onboarding process. Using an event-driven architecture, a reactive Kotlin backend, and reliable AMQP messaging, new users are provisioned across systems automatically—creating a seamless onboarding experience without additional user interaction.
Why This Approach? Our Core Principles
Before diving into the "how," let's explore the "why." Our architectural design was guided by four key principles for our marketplace:
Effortless User Experience: We aimed to eliminate friction. Signing up and getting ready for payments should feel like one smooth, natural action.
Unwavering Brand Consistency (Whitelabeling): While utilizing powerful third-party tools like
Stripe Connect, our users should always feel they're within our platform's environment.
Unyielding Security: Think of Keycloak as our trusted guardian—it's our central source of truth for identity and authorization.
Architectural Flexibility: We built a modular system where components can be updated or replaced without requiring major overhauls.
These core principles naturally led us to an event-driven model, where Keycloak's identity-related actions seamlessly trigger payment-specific processes.
The Full Picture: A Detailed User's Journey & Backend Magic
To truly appreciate the "invisible" aspect, let's trace a typical user registration, observing how the frontend experience and backend processes flawlessly dance together across three distinct phases.
Phase 1: User Registration & Event Emission
In this first phase, users interact with the frontend and Keycloak authentication system, which culminates when Keycloak emits the crucial USER_CREATED event.
User Arrives: The user starts the signup process through our Next.js frontend.
Auth Redirect: The frontend redirects securely to Keycloak for authentication/registration.
Keycloak Magic: The user completes their registration on Keycloak's standard pages.
Back to App: Keycloak returns the user to our frontend with a JWT for login.
Keycloak Event: Keycloak generates a USER_CREATED event internally when registration succeeds.
Webhook & AMQP: Our Keycloak Webhook plugin listens for this event and sends it as an AMQP message to a broker.
Phase 2: Asynchronous Backend Processing
This phase details the event's journey from the message broker to the Payment Service, and its interaction with Stripe and our database to provision the payment account.
/Asynchronous-Backend-Processing
1graph TD
2MQ[AMQP Message Broker] -->|7- Payment Service Consumes Event| PS["Payment Service (Spring Boot Kotlin)"] PS -->|8- Calls Stripe API to Create Account| S[Stripe]
3S -->|Stripe Account ID| PS
4PS -->|9- Saves KeycloakUser ID : StripeAccount ID Mapping| DB[DynamoDB]
Explanation:
Service Listens: Our Payment Service monitors and processes the USER_CREATED event from the AMQP queue.
Stripe Account Creation: Using the event's user information, the Payment Service creates a new Stripe Connect account via the Stripe API.
Record Keeping: The system stores the new Stripe Account ID in DynamoDB, linking it directly to the Keycloak User ID.
Phase 3: Subsequent User Payment Setup
This final phase shows how users complete their payment details through the frontend interface, building seamlessly on the backend's groundwork to maintain a consistent brand experience.
/Subsequent-User-Payment-Setup
1graph TD
2U[User] -->|10- Navigates to Payment Setup Section| F[Next.js Frontend]
3F -->|"11- Frontend Requests Payment Status (API Call)"| PS[Payment Service]
7F -->|"13- Embeds Stripe Elements (Guided Onboarding within UI)"| S["Stripe (via Frontend)"]
Explanation:
User Explores & Configures: When ready, the user visits the payment setup section in our frontend.
Frontend Queries Status: The frontend sends a secure API request to the Payment Service to check the payment configuration status.
Backend Delivers: The Payment Service fetches the pre-created Stripe Account ID from DynamoDB.
Guided Onboarding: Using the Stripe Account ID, our Next.js frontend implements Stripe Elements to walk the user through the remaining steps (like bank details and KYC) within our branded UI. The user stays within our application throughout the process.
This phased approach means that when users are ready to handle payments, most of the setup work is already complete behind the scenes, creating a smooth and efficient experience.
Keycloak's Event Superpower: The Webhook and AMQP Bridge
The key to our background magic lies in Keycloak's powerful event system, which emits notifications for various user lifecycle actions. The Keycloak Webhook plugin is configured to capture these specific events.
When an event like USER_CREATED occurs, rather than making a direct, blocking HTTP call to our Payment Service, the plugin transforms the event data into a message and publishes it to an AMQP broker (like RabbitMQ).
Why AMQP? A Strategic Choice for Robustness:
Decoupling: Keycloak runs independently without needing to know the Payment Service's network address or availability status.
Resilience: If the Payment Service goes down, messages wait safely in the AMQP broker and process automatically when service resumes—no events are lost.
Scalability: Multiple Payment Service instances can consume from the same queue, making horizontal scaling straightforward.
The basic flow for this event relay is elegantly simple:
With Keycloak events reliably flowing via AMQP, our Payment Service assumes the role of the orchestrator for Stripe Connect account management.
Scenario 1: User Creation or Update
When a USER_CREATED (or USER_UPDATED, if we sync changes) event arrives:
The Payment Service receives the event data from the AMQP queue.
It extracts the relevant user details (such as Keycloak User ID and email) from the event payload.
It calls the Stripe API to create a new Connect account (typically an Express account), pre-filling information from Keycloak and marketplace defaults.
Upon successful creation, it securely stores the Stripe Account ID in DynamoDB, mapped to the Keycloak User ID.
This streamlined process ensures efficient and seamless integration:
// language: mermaid
// title: /User Creation or Update
// copy: true
// lines: true
// maxLines: 10
sequenceDiagram
participant KPlugin as Keycloak Webhook via AMQP
participant PaySvc as Payment Service
participant StripeAPI as Stripe
participant DB as DynamoDB
KPlugin->>PaySvc: USER_CREATED Event
activate PaySvc
PaySvc->>StripeAPI: Create Connect Account (User Details)
activate StripeAPI
StripeAPI-->>PaySvc: Stripe Account ID
deactivate StripeAPI
PaySvc->>DB: Save (KeycloakUserID, StripeAccountID)
activate DB
DB-->>PaySvc: Confirmation
deactivate DB
deactivate PaySvc
Scenario 2: User Deletion
When a USER_DELETED event is received:
The Payment Service queries DynamoDB to retrieve the user's corresponding Stripe Account ID using their Keycloak User ID.
If an account is found, it calls the Stripe API to delete or deactivate the Connect account (based on compliance requirements and data retention policies).
Finally, it removes the mapping entry from DynamoDB, maintaining a clean and consistent state.
The flow for deletion is equally precise:
// language: mermaid
// title: /User Deletion
// copy: true
// lines: true
// maxLines: 10
sequenceDiagram
participant KPlugin as Keycloak Webhook via AMQP
participant PaySvc as Payment Service
participant StripeAPI as Stripe
participant DB as DynamoDB
KPlugin->>PaySvc: USER_DELETED Event
activate PaySvc
PaySvc->>DB: Get StripeAccountID for KeycloakUserID
activate DB
DB-->>PaySvc: Stripe Account ID (or null)
deactivate DB
alt Stripe Account ID Found
PaySvc->>StripeAPI: Delete Connect Account (StripeAccountID)
activate StripeAPI
StripeAPI-->>PaySvc: Confirmation
deactivate StripeAPI
PaySvc->>DB: Delete Mapping (KeycloakUserID)
activate DB
DB-->>PaySvc: Confirmation
deactivate DB
end
deactivate PaySvc
Under the Hood: The Payment Service (Spring Boot Kotlin)
Our Payment Service is a Spring Boot application built in Kotlin that leverages coroutines for efficient async operations. This non-blocking approach is essential for handling I/O-bound tasks like AMQP messaging, DynamoDB access, and Stripe API calls—ensuring optimal performance and responsiveness.
AMQP Event Listener: The Dedicated Consumer
At the heart of our service, the KeycloakEventListener monitors the designated AMQP queue for incoming events:
Core Service Logic (Simplified createAccountForUser): The Account Alchemist
This method encapsulates the essential logic for creating a Stripe account, including an important idempotency check to prevent duplicate creations:
// language: kotlin
// title: /createAccountForUser
// copy: true
// lines: true
// maxLines: 10
@Service
class PaymentAccountOrchestrator(
private val stripeService: StripeService, // Robust wrapper for all Stripe API calls
private val userRepository: UserRepository // Our DynamoDB repository for user-Stripe mappings
) {
suspend fun createAccountForUser(keycloakUserId: String) {
// Idempotency check: Crucial to avoid recreating existing accounts
if (userRepository.findStripeAccountId(keycloakUserId) != null) {
logger.info("Stripe account mapping already exists for Keycloak user: $keycloakUserId. Skipping creation.")
return
}
// Build the request for an Express account with sensible defaults
val accountCreationRequest = StripeAccountCreateRequest(
type = "express",
country = "DE", // Example default country (e.g., for a German marketplace)
email = "user-$keycloakUserId@example.com" // Placeholder; ideally fetched from Keycloak user details
// Add other required capabilities (e.g., card_payments, transfers) as needed
)
val stripeAccount = stripeService.createAccount(accountCreationRequest)
userRepository.saveStripeMapping(keycloakUserId, stripeAccount.id)
logger.info("Successfully created Stripe account ${stripeAccount.id} for Keycloak user $keycloakUserId.")
}
// ... Implement other core methods like deleteAccountForUser to complete the lifecycle
}
The Frontend Experience: Whitelabeling in Action
As rigorously highlighted in the full user journey, the end-user is meticulously shielded from seeing any raw Stripe pages. After the initial, silent account creation:
Our Next.js frontend securely obtains the user's Stripe Account ID (fetched from our backend's protected API).
It then strategically leverages Stripe Elements or other embedded UIs provided by Stripe.js to collect any further required information (e.g., bank account details for payouts, or identity verification documents for KYC/KYB compliance).
Critically, this entire data collection process occurs within our application's UI, preserving seamless brand consistency and providing a guided, familiar user experience.
This meticulous approach is the very essence of our "whitelabeling" goal: harnessing Stripe's powerful backend payment processing capabilities without compromising our carefully crafted frontend user experience.
The Payoff: Seamless, Secure, and Flexible
Our event-driven, decoupled architecture delivers on our initial design principles:
Effortless UX: User registration triggers automatic payment account creation, eliminating friction and creating a responsive platform experience.
Brand Consistency: The frontend maintains complete control over Stripe interaction points, keeping all user touchpoints within our branded UI.
Robust Security: Keycloak acts as the central gatekeeper for identity and access management, while all sensitive payment operations are handled securely on the server side.
Exceptional Flexibility: The AMQP bridge keeps Keycloak and the Payment Service loosely coupled. All Stripe interactions are contained within the Payment Service, simplifying future changes or provider switches.
This "invisible onboarding" approach streamlines the initial user experience, making our platform feel more polished, professional, and intuitive from the start.
Final Thoughts
Let's face it - combining identity management with payment systems is like trying to teach a cat to do your taxes. But we cracked it! By letting Keycloak and our async architecture do their dance (think Fred Astaire meets distributed computing), we've created something so smooth, it's like your payments are being processed by ninja accountants. Sure, setting it all up was about as fun as debugging code written by a caffeinated squirrel, but the results are worth it. We've turned what could have been a DMV-level experience into something so invisible, it makes Harry Potter's cloak look amateur. Now users can join our platform faster than you can say "blockchain" - and that's saying something!