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-Registration-&-Event-Emission
1graph TD
2U[User] -->|"1- Initiates Signup"| F[Next.js Frontend]
3F -->|2- Redirects for Authentication| KC["Keycloak (Auth/Register)"]
4KC -->|3- User Completes Registration| F
5F -->|4- Receives JWT & Logs In| U
6KC -->|5- Emits USER_CREATED Event| W[Keycloak Webhook Plugin]
7W -->|6- Publishes AMQP Message| MQ[AMQP Message Broker]
phase 1 image

Explanation:

  1. User Arrives: The user starts the signup process through our Next.js frontend.

  2. Auth Redirect: The frontend redirects securely to Keycloak for authentication/registration.

  3. Keycloak Magic: The user completes their registration on Keycloak's standard pages.

  4. Back to App: Keycloak returns the user to our frontend with a JWT for login.

  5. Keycloak Event: Keycloak generates a USER_CREATED event internally when registration succeeds.

  6. 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]
Phase 2 image

Explanation:

  1. Service Listens: Our Payment Service monitors and processes the USER_CREATED event from the AMQP queue.

  2. Stripe Account Creation: Using the event's user information, the Payment Service creates a new Stripe Connect account via the Stripe API.

  3. 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]
4PS -->|12- Fetches Stripe Account ID| DB[DynamoDB]
5DB -->|Stripe Account ID| PS
6PS -->|Returns Stripe Account ID| F
7F -->|"13- Embeds Stripe Elements (Guided Onboarding within UI)"| S["Stripe (via Frontend)"]
Phase 3

Explanation:

  1. User Explores & Configures: When ready, the user visits the payment setup section in our frontend.

  2. Frontend Queries Status: The frontend sends a secure API request to the Payment Service to check the payment configuration status.

  3. Backend Delivers: The Payment Service fetches the pre-created Stripe Account ID from DynamoDB.

  4. 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:

/The-Webhook-and-AMQP-Bridge
1graph TD
2KC[Keycloak: Event Occurs] --> Plugin[Webhook Plugin]
3Plugin -- Publishes Event --> AMQP[AMQP Message Broker]
4AMQP -- Delivers Message --> PS[Payment Service: AMQP Consumer]
5PS --> Logic[Processes Event]

 The Webhook and AMQP Bridge
Automating Stripe Connect: The Core Logic

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:

  1. The Payment Service receives the event data from the AMQP queue.

  2. It extracts the relevant user details (such as Keycloak User ID and email) from the event payload.

  3. It calls the Stripe API to create a new Connect account (typically an Express account), pre-filling information from Keycloak and marketplace defaults.

  4. 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

Scenario1

Scenario 2: User Deletion

When a USER_DELETED event is received:

  1. The Payment Service queries DynamoDB to retrieve the user's corresponding Stripe Account ID using their Keycloak User ID.

  2. 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).

  3. 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

Scenario2

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:

/KeycloakEventListener
1@Component
2class KeycloakEventListener(
3 private val paymentAccountOrchestrator: PaymentAccountOrchestrator
4) {
5 @RabbitListener(queues = ["\\\\${app.amqp.keycloak-events-queue}"]) // Configured queue name
6 suspend fun handleKeycloakEvent(eventMessage: KeycloakEventPayload) { // Assuming deserialized payload
7 logger.info("Received Keycloak event: ${eventMessage.type} for user ${eventMessage.details.userId}")
8 when (eventMessage.type) {
9 "USER_CREATED" -> paymentAccountOrchestrator.createAccountForUser(eventMessage.details.userId)
10 "USER_DELETED" -> paymentAccountOrchestrator.deleteAccountForUser(eventMessage.details.userId)
11 // Handle USER_UPDATED if necessary for syncing profile changes
12 else -> logger.warn("Unhandled Keycloak event type: ${eventMessage.type}")
13 }
14 }
15}

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!

© 2026 adorsys. Alle Rechte vorbehalten.
Certificate TopCompany Kununu
Certificate ISO 27001
Certificate ISO 9001