How the Webhooks module is built - queue pipeline, database schema, and packaging
The delivery pipeline
- Event capture. The ten curated events are wired to a single shared observer that snapshots entity data at observe time. Snapshotting is why delete events still carry full entity data at delivery, and why payloads reflect the state when the event fired rather than when the consumer ran. Custom events use a separate
afterplugin on the concrete event manager’s dispatch method, inert unless enabled in configuration. - Payload building. The snapshot is converted to flat template variables. Orders are enriched with their lazy-loaded relations (items, billing and shipping addresses, payment) that a plain
toArray()would miss; shipments with tracks and items. A key blacklist strips sensitive fields during conversion. - Queueing. The observer publishes to the
ocmlabs.webhook.triggerqueue on thedbconnection. The synchronous work in the request path ends here. - Dispatch. The consumer hands each message to a single dispatcher - the same entry point used by real deliveries, cron retries, and the admin Send Test. It validates the URL (SSRF guard), renders the payload and custom headers through sandboxed Smarty, signs the body (HMAC-SHA256, header omitted when no secret), applies Bearer/Basic auth, and POSTs via Guzzle with the configured timeout.
- Logging and retries. Every attempt writes a delivery log row. Retryable failures store the serialized message in the retry table with a
next_attempt_atcomputed by exponential backoff; a per-minute cron re-publishes due retries without re-snapshotting. Template render errors are deterministic and never retried.
Database
Three tables:
ocmlabs_webhook- subscriptions: name, event, URL, payload template, content type, custom headers (JSON), HMAC secret and auth credentials (encrypted), status, store scope.ocmlabs_webhook_delivery_log- one row per attempt: delivery UUID, attempt number, status (success/failed/retrying), HTTP code, error message, request body (only when payload logging is on, 64 KB cap), response body (4 KB cap), duration. Cascade-deletes with its webhook.ocmlabs_webhook_retry- the pending-retry queue: serialized message plus thenext_attempt_attimestamp the retry cron polls on.
Cron jobs
| Job | Schedule | Purpose |
|---|---|---|
ocmlabs_webhook_retry | every minute | Re-publish due retries |
ocmlabs_webhook_prune_log | 02:30 daily | Delete delivery log rows older than the configured retention |
Admin surface
Route frontName ocmlabs_webhook, menu under System > OCM Labs: a webhooks grid and form (with synchronous Send Test and a live template-variables endpoint), plus a read-only delivery log grid and detail view. ACL resources: OCMLabs_Webhook::manage (with a delete child), OCMLabs_Webhook::log, and OCMLabs_Webhook::config. A data patch installs ten disabled sample webhooks, one per curated event, each carrying that event’s default payload template.
Sample-data parity
The “Available Template Variables” preview and Send Test resolve their sample data through the same payload-building pipeline real deliveries use. What the preview documents, what a test renders against, and what production delivers cannot drift apart - including enriched order relations like order.billing_address.
Packaging
Distributed as two Composer packages from one repository:
| Package | Type | Purpose |
|---|---|---|
ocmlabs/module-webhook | magento2-module | The module itself. Installs on Magento Open Source and Adobe Commerce. |
ocmlabs/module-webhook-commerce | metapackage | Adobe Commerce licensing gate. Requires the community package plus magento/product-enterprise-edition. |
Dependencies: smarty/smarty ^5.8, guzzlehttp/guzzle ^7.5, magento/framework 103.0.7 - 103.0.8. The module declares no REST or GraphQL API for managing subscriptions - webhooks are configured through the admin UI.