This document explains how to implement the webhook handling functionality in your shop plugin.
📘 Architecture & Best Practices
This guide focuses on the practical implementation steps.
To understand the underlying logic—including concurrency control, two-stage locking, and race condition prevention—please read the Architecture Overview first. It explains why these components exist and how they ensure data integrity.
The system is designed to process incoming webhooks by mapping them to specific actions. This is achieved by working with several components:
SettingsProviderInterface/DefaultSettingsProvider: Provides essential configuration (Space ID, User ID, etc.) toplugin-core. Plugins extendDefaultSettingsProviderto provide these values.Settings: An object inplugin-corethat fetches and validates configuration.SdkProvider: A service inplugin-corethat usesSettingsto create a configured PostFinanceCheckout SDKApiClient.StateFetcherInterface: An interface for getting the webhook's current (remoteState).plugin-coreprovides aDefaultStateFetcher(which uses theSdkProvider).WebhookLifecycleHandler: The bridge between the core engine and your shop's infrastructure. It handles locking, transactions, and tracking progress.StateMapperInterface: An optional interface a plugin can implement to "translate" betweenplugin-core's standard states (e.g.,COMPLETED) and the application's own custom state names (e.g.,wc-processing).WebhookProcessor: The main service that orchestrates the entire process, calling theWebhookLifecycleHandlerhooks at the appropriate times.Listener: Provides the correctCommandfor a specific webhook event.Command: Contains the pure business logic (e.g., creating an invoice). It receives the full event details via aWebhookContextobject.LoggerInterface: The plugin must provide a PSR-3 compatible logger implementation (or adapter) so the core can log debug information and errors.
The plugin developer's responsibility is to create the concrete implementations for these components.
The developer must create a class that extends DefaultSettingsProvider to provide the necessary API credentials and Space ID.
The developer must create a class that extends DefaultWebhookLifecycleHandler.
Instead of writing complex locking logic manually, you simply tell the handler what to lock and how to lock it:
getLastProcessedState(): Look up the last processed state from yourwebhook_progressdatabase table.getLockableResources(): Return a list of unique IDs to lock (e.g., the Webhook Entity ID and the Shop Order ID).doAcquireLock()/doReleaseLock(): Implement the actual calls to your shop's locking system (e.g., Redis lock, DB lock).preProcess()/postProcess(): (Optional) Override these only if you need to wrap the execution in a Database Transaction. Always call parent to ensure locking runs.
If the application's state names differ from plugin-core's, create a class that implements StateMapperInterface.
A Command contains the pure business logic.
- It SHOULD: Modify shop resources (Orders, Invoices) based on the webhook data.
- It SHOULD NOT: Manage the
webhook_progressstate or handle low-level locking (this is done by the Lifecycle Handler).
Important: Commands must follow the "Safe Update" pattern. Always reload the resource (Order) from the database to ensure it isn't stale, and check for protected states (e.g., "Payment Review") before overwriting status. See the Architecture Overview document for more information.
A Listener connects a webhook event to a Command. Its getCommand() method receives the WebhookContext and creates the Command.
In the application's initialization logic (e.g., Magento's di.xml via a factory), the Listener is added to a central WebhookListenerRegistry.
Using the platform's DI system (e.g., di.xml):
- Set a preference for
SettingsProviderInterface(Step 1). - Set a preference for
WebhookLifecycleHandler(Step 2). - Set a preference for
StateFetcherInterface. - Configure the
WebhookProcessorto inject these components, along with theListenerRegistryandLogger. - The
WebhookProcessoris then injected into the controller and itsprocess()method is called.
See the example directory for a simulated webhook processing flow:
- Initialize services.
- Register listeners for different entities and states.
- Simulate incoming webhook requests and observe the processing lifecycle (locking, catch-up logic, command execution).