← All insights
Architecture Integrations 7 min read

50+ Providers, Zero Coupling

How a payment provider callback format change almost broke our bonus engine — and the module isolation pattern that made sure it never could again.

W
WebPrefer Engineering
November 2025
Integration Architecture
50+ Providers, Zero Coupling
PAM Core
Contracts only — doesn't know what providers exist
↕ typed interfaces
PaymentIQ
Payment
Hexopay
Payment
WorldLine
Payment
SportTech
Games
PlayStar
Games
AleaPlay
Games
Twilio
SMS
SendGrid
Email
FastTrack
CRM
DevCode ID
SSO
+ 40 more providers over the platform's lifetime
Each module isolated — a provider API change touches only its own module

Over PAM's lifetime, we've integrated more than fifty third-party providers: payment processors, game providers, KYC vendors, CRM tools, communication services, lotteries, and sports data feeds. Each one has its own API format, its own callback conventions, its own failure modes, and its own upgrade schedule that has nothing to do with yours.

The challenge isn't adding a new integration. It's making sure that every integration you've already built stays working while you add the next one — and that when a provider changes something on their end, the blast radius is exactly one module.

We didn't start there. We learned our way there.

How integrations start

The first payment provider was PaymentIQ. The integration was built directly into the core platform. The callback handler knew about the deposit flow. The deposit flow knew about PaymentIQ's response format. When a deposit completed successfully, the same code path that parsed the PaymentIQ response also triggered the bonus evaluation and sent the confirmation email.

This worked. For one provider.

Adding the second payment provider required branching the callback handler. Adding game providers required adding new callback endpoints that touched the casino module directly. Communication providers were called inline from the business logic that needed them. The integration layer, such as it was, grew by accretion — each new provider wired into the nearest thing that needed it.

When it broke

A payment provider updated their callback format. A field was renamed. Our callback parser failed to read it, and since the callback parser was tightly coupled to the deposit completion logic, deposits appeared to complete but bonus evaluations never fired. Players who deposited during a promotion received no bonus. The failure mode was silent — no exception, just a missing trigger. It took hours to trace because the callback format change and the bonus failure looked like separate issues.

The coupling problem

The root cause wasn't that the provider changed their format. Providers change their format. That's normal. The root cause was that our handling of the provider's format was entangled with business logic that had nothing to do with the provider.

A payment callback parser's only job is to parse a payment callback. It should not know that a successful parse triggers a bonus evaluation. The bonus evaluation should not know where the deposit signal came from. The communication service should not be called by whichever module happened to need a confirmation email sent.

These are separate concerns. Treating them as one concern is how a renamed field in a provider's JSON breaks your bonus engine.

The module isolation pattern

The integration layer in the current generation of PAM is structured around a clear principle: every provider integration is a self-contained, independently deployable module. The module boundary enforces what the module is allowed to know about.

Each integration module exposes a set of inbound endpoints (for provider callbacks) and implements a typed contract interface that the platform core can call. The module is the only code that knows what the provider's API looks like. The platform core doesn't know — and can't know — what format PaymentIQ uses for callbacks. It only knows what a completed deposit looks like from the platform's perspective.

// What the platform core sees — provider-agnostic interface IPaymentProvider { Task<DepositResult> ProcessDepositAsync(DepositRequest request); Task<WithdrawalResult> ProcessWithdrawalAsync(WithdrawalRequest request); } // What PaymentIQ module implements — all provider detail stays here class PaymentIqProvider : IPaymentProvider { // Knows PaymentIQ's format. Nothing else in the platform does. }

When a provider changes their callback format, the fix is entirely inside the module. Nothing else needs to know it happened. The platform core continues calling ProcessDepositAsync and receives a DepositResult. The DepositResult is generated by the module after parsing whatever format the provider sent. The rest of the platform doesn't care what that format was.

Independent deployment

The module isolation goes further than just code separation. In the current architecture, each integration module is an independently deployable unit. Adding a new provider doesn't require a platform-wide deployment. Updating an existing provider's module doesn't require coordinating with other modules or restarting services that don't need to change.

This matters operationally. A game provider integration update that doesn't touch the payment layer or the bonus engine should not require testing those layers. It should not require a platform-wide deployment window. The deployment surface should match the change surface.

What fifty-plus integrations looks like in practice

Over PAM's lifetime, the integration surface has covered:

Each of these integrations has had at least one breaking change in its lifetime. Format updates, authentication model changes, endpoint deprecations, new required fields. In every case, the fix has been inside the module. Nothing else moved.

Provider failure isolation

The isolation model also affects failure behavior. When a game provider goes down, the module that handles that provider returns an appropriate error to the platform core. The platform core handles it. Other game providers keep working. Payment flows keep working. The bonus engine keeps working.

In the old tightly-coupled architecture, a provider failure could propagate in unexpected ways — because the provider's code was mixed into flows that shouldn't depend on it. With module isolation, the failure boundary is the module boundary.

Today

When a provider changes their API, we update one module. We write tests for that module. We deploy that module. Nothing else is tested, touched, or at risk. The platform core doesn't know the change happened. That's the right outcome — a provider API update should have exactly as much impact as it deserves, which is as little as possible.

This isn't a microservices pitch

Module isolation doesn't require microservices. PAM's integration modules are deployed as part of the same host process for many providers. The isolation is at the code and dependency level — not necessarily at the network level. The key is that the module boundary is respected: code inside the module is the only code that knows about the provider's details.

The test for whether you have real isolation is simple: can you update a provider's integration without running tests for anything else? If the answer is no — if your test suite has cross-provider dependencies, or if the deployment pipeline requires platform-wide smoke tests for a single provider update — you have coupling you haven't named yet.

Share this insight
Share on LinkedIn
Preview post text
More insights
Get in Touch

Ready to see it?

We offer live demos scoped to your specific operation type — whether you're launching a new brand, migrating from an existing platform, or evaluating options for a white-label deployment.

Address
Wahlbecksgatan 8, 582 13 Linköping, Sweden
Mikael Lindberg Castell
mikael@webprefer.com
CEO & Founder, WebPrefer AB