ytec / module-webhook
Configurable outgoing webhooks for Magento 2 with Liquid payload templating, conditions, authorization, retries and delivery logs.
Package info
github.com/matheusmarqui1/magento2-ytec-webhook
Type:magento2-module
pkg:composer/ytec/module-webhook
Requires
- php: ~8.1.0||~8.2.0||~8.3.0
- ext-curl: *
- liquid/liquid: ^1.4.8
- ytec/base: *
README
Configurable outgoing webhooks for Magento 2.
Ytec_Webhook lets a store define, from the admin panel, HTTP callbacks that fire when sales events happen. Each webhook has its own target URL, method, headers, authorization scheme, Liquid-templated payload, trigger conditions, store scoping and retry policy. Deliveries are queued, retried with configurable backoff, and every attempt is logged with the full request/response for auditing.
- Vendor:
ytec/module-webhook - Version: 1.0.0
- Magento: 2.4.x (Open Source & Commerce)
- PHP: 8.1 / 8.2 / 8.3
- License: MIT
Table of contents
- Features
- Requirements
- Installation
- Configuration
- Triggers
- Delivery lifecycle
- Payload templating
- Conditions
- Authorization
- Retries
- Logging and retention
- Health dashboard
- Test endpoint
- CLI commands
- Cron jobs
- Message queue
- ACL resources
- Database schema
- Extending the module
Features
- Admin-managed webhooks. Create, duplicate, enable/disable and delete webhook configurations from a grid + form UI, no code or deployment required.
- Liquid payload templating. Bodies and URLs are Liquid templates rendered against the triggering entity, with an in-form variable suggestion panel built by reflecting the Magento sales API interfaces and the entity's database columns.
- Condition builder. Nested AND/OR condition groups with 21 operators decide whether a given entity actually fires the webhook.
- Six authorization schemes. None, Basic, Bearer, API key, OAuth 1.0a (signed) and OAuth 2.0 (client credentials, with token fetching).
- Asynchronous delivery. Messages are published to a queue and delivered by a consumer, so the checkout/admin request is never blocked. A per-webhook "send immediately" flag is available when synchronous delivery is required.
- Configurable retries. Per-webhook retry toggle, max attempts, explicit interval sequence and the list of HTTP status codes considered retryable.
- Full delivery log. Every attempt stores URL, method, headers, body, response status, response headers, response body, duration in milliseconds and any error, with a fulltext index for searching.
- Health dashboard. Success/failure rates and delivery trends over time.
- Manual retry. Retry a failed message from the admin grid, individually or in bulk.
- Store scoping. Each webhook can be limited to specific store views.
- Built-in test endpoint. A REST endpoint that answers 200 most of the time and 500/504 occasionally, so retry behaviour can be exercised without a third-party server.
- Simulation command. Generate real webhook messages in bulk from the CLI to load-test a configuration.
Requirements
- Magento 2.4.x (Open Source or Commerce)
- PHP 8.1, 8.2 or 8.3
ext-curlytec/base(provides the sharedModuleConfigurationand the admin menu/ACL root)liquid/liquid^1.4.8- A running message-queue consumer (AMQP/RabbitMQ or the DB queue) for asynchronous delivery
- Magento cron running, for retries and log cleanup
Installation
Composer (from this repository)
composer config repositories.ytec-webhook vcs https://github.com/matheusmarqui1/magento2-ytec-webhook composer require ytec/module-webhook
Manual
Copy the module to app/code/Ytec/Webhook, then install the ytec/base and liquid/liquid dependencies with Composer.
Enable
bin/magento module:enable Ytec_Base Ytec_Webhook bin/magento setup:upgrade bin/magento setup:di:compile bin/magento setup:static-content:deploy bin/magento cache:flush
Start the queue consumer (or let consumers_runner do it via cron):
bin/magento queue:consumers:start ytec.webhook.message.send
Configuration
Stores → Configuration → Ytec → Webhook (ytec_webhook):
| Path | Default | Scope | Description |
|---|---|---|---|
ytec_webhook/general/enabled |
0 |
Store view | Master switch for the module. |
ytec_webhook/logs/enabled |
1 |
Default | Record a log row per delivery attempt. |
ytec_webhook/logs/retention_hours |
730 |
Default | Hours to keep logs before the cleanup cron deletes them (~30 days). |
Webhooks themselves are managed under Ytec → Webhook → Webhook Configurations. Each configuration holds:
- name, enabled flag and target store views
- the trigger event
- request configuration: URL, HTTP method, headers, body template and authorization
- payload parsing timing
- trigger conditions
- retry policy: enabled, max retries, interval sequence, retryable status codes
Triggers
Webhooks are bound to one sales event each:
| Trigger | Magento event | Fires when |
|---|---|---|
| Invoice creation | sales_order_invoice_save_after |
An invoice is saved |
| Invoice pay | sales_order_invoice_pay |
An invoice is paid |
| Invoice register | sales_order_invoice_register |
An invoice is registered against the order |
| Credit memo creation | sales_order_creditmemo_save_after |
A credit memo is saved |
| Credit memo refund | sales_order_creditmemo_refund |
A credit memo is refunded |
Entity types understood by the payload/entity loader are invoice, order, shipment and credit_memo.
Delivery lifecycle
- An observer catches the sales event and hands the entity to
WebhookTriggerHandler. - The handler loads every enabled webhook configuration bound to that trigger and applicable to the entity's store, deduplicating within the request.
- Conditions are evaluated against the entity. A webhook whose conditions do not match is skipped.
- A
ytec_webhook_messagerow is created with statuspending. - The message is published to the
ytec.webhook.message.sendqueue, unless the configuration has send immediately enabled, in which case it is sent inline. - The consumer renders the payload (if the parsing timing is at sending), applies authorization, performs the request and writes a log row.
- On success the message becomes
processedanddelivered_atis stamped. On a retryable failure it becomesfailedwith anext_retry_at. Once the attempts are used up it becomesexhausted.
Message statuses: pending, processing, processed, failed, exhausted, error.
Payload templating
Bodies and URLs are Liquid templates. The admin form ships a payload editor and a URL editor with a variable-suggestion panel; suggestions are derived at runtime from the relevant Magento interface (OrderInterface, InvoiceInterface, CreditmemoInterface, ShipmentInterface, their item and address interfaces) plus the entity table columns, so they stay accurate even with custom attributes.
{
"invoice": "{{ invoice.increment_id }}",
"order": "{{ invoice.order.increment_id }}",
"grand_total": {{ invoice.grand_total }},
"customer_email": "{{ invoice.order.customer_email }}",
"items": [
{% for item in invoice.items %}
{ "sku": "{{ item.sku }}", "qty": {{ item.qty }} }{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
URLs are templated too, so a webhook can post to a per-entity path:
https://api.example.com/webhook/{{ invoice.increment_id }}
Parsing timing
| Value | Behaviour |
|---|---|
webhook_trigger |
Render the body when the event fires and store the rendered payload on the message. The payload reflects the entity exactly as it was at trigger time. |
at_sending |
Render the body when the message is actually delivered. The payload reflects the entity's current state, which matters for delayed or retried deliveries. |
Conditions
Conditions are nested groups combined with AND / OR, each leaf comparing a resolved entity field against a value.
Available operators: eq, neq, gt, gte, lt, lte, contains, not_contains, starts_with, ends_with, regex, in, not_in, is_null, is_not_null, is_empty, is_not_empty, is_true, is_false.
Field paths use the same dotted notation as the payload templates, for example invoice.order.customer_group_id or invoice.grand_total.
Authorization
Selected per webhook in the authorization editor. Secret values are stored encrypted.
| Type | What it does |
|---|---|
none |
No authorization header. |
basic |
HTTP Basic with username/password. |
bearer |
Authorization: Bearer <token>. |
api_key |
Key/value pair sent as a header or query parameter. |
oauth1 |
OAuth 1.0a request signing (RFC 5849). |
oauth2 |
OAuth 2.0 client credentials: fetches an access token from the token endpoint and attaches it. |
New schemes are added by implementing AuthorizationApplierInterface and registering the class in the AuthorizationApplierFactory DI pool.
Retries
Retries are configured per webhook:
- Retry enabled toggles the whole mechanism.
- Max retries caps the number of automatic attempts (default 5).
- Retry intervals is a comma-separated list of delays in seconds. Attempt n uses interval n; once the list is exhausted, the last value repeats. For example
60,300,900means retry after 1 minute, then 5 minutes, then every 15 minutes. Left empty it falls back to60,300,900,3600,14400. - Retryable status codes is a comma-separated list of HTTP status codes that count as retryable; anything else is a permanent failure. Left empty it falls back to
408,429,500,502,503,504.
The ytec_webhook_retry_scheduler cron runs every minute and re-queues messages whose next_retry_at has passed. Manual retries from the admin grid are counted separately from automatic ones.
Logging and retention
Every attempt writes a ytec_webhook_message_log row containing the request URL, method, headers and body, the response status, headers and body, the round-trip duration in milliseconds, a success flag and any error message. A fulltext index over the URL, bodies and error message makes the log grid searchable.
Logs older than ytec_webhook/logs/retention_hours are deleted daily at 03:00 by the ytec_webhook_log_cleanup cron. Logging can be turned off entirely.
Health dashboard
Ytec → Webhook → Health Dashboard charts delivery volume and success/failure rates over time, per webhook configuration. The chart library is loaded from a CDN; on a store with a strict CSP or no outbound access, either allow the CDN or bundle the library locally.
Test endpoint
The module exposes a REST endpoint that simulates a flaky third party, useful for exercising retry configuration end to end:
POST /rest/V1/ytec-webhook/test
GET /rest/V1/ytec-webhook/test
It answers 200 roughly 98% of the time and 500 or 504 the rest of the time. It is protected by the Ytec_Webhook::webhook_test ACL resource.
CLI commands
# Run the retry scheduler on demand (processes messages due for retry) bin/magento ytec:webhook:retry-scheduler # Create real webhook messages for a configuration, to load-test it bin/magento ytec:webhook:simulate \ --id=1 \ # -i webhook configuration ID (required) --count=50 \ # -c number of messages to create --delay=100 \ # -d delay between creations, in milliseconds --start=1000 \ # -s starting entity ID for the simulated entities --entity=invoice \ # -e invoice | order | shipment | credit_memo --immediate # send inline instead of publishing to the queue
Cron jobs
| Job | Schedule | Purpose |
|---|---|---|
ytec_webhook_retry_scheduler |
* * * * * |
Re-queue messages whose retry time has come. |
ytec_webhook_log_cleanup |
0 3 * * * |
Delete logs past the retention window. |
Message queue
| Topic | Exchange | Queue | Consumer |
|---|---|---|---|
ytec.webhook.message.send |
magento |
ytec.webhook.message.send |
ytec.webhook.message.send |
ACL resources
Nested under Ytec_Base::base_module:
Ytec_Webhook::webhook
├── Ytec_Webhook::webhook_config
│ ├── Ytec_Webhook::webhook_config_save
│ └── Ytec_Webhook::webhook_config_delete
├── Ytec_Webhook::webhook_dashboard
├── Ytec_Webhook::webhook_messages
├── Ytec_Webhook::webhook_logs
├── Ytec_Webhook::webhook_test
└── Ytec_Webhook::webhook_alerting
├── Ytec_Webhook::webhook_alerting_manage
└── Ytec_Webhook::webhook_alerting_config
The webhook_alerting branch is declared here as an extension point for a companion alerting module; it is inert without one.
Database schema
| Table | Purpose |
|---|---|
ytec_webhook_config |
Webhook definitions: name, enabled, store IDs, trigger, send-immediately flag, parsing timing, request config (JSON), conditions (JSON) and retry policy. |
ytec_webhook_message |
One row per triggered delivery: config, trigger, entity reference, status, rendered body, automatic/manual retry counters, next retry and delivery timestamps. Cascades on config delete. |
ytec_webhook_message_log |
One row per attempt: attempt number, request URL/method/headers/body, response status/headers/body, duration, success flag and error message. Cascades on message and config delete. |
Extending the module
The module is built around interfaces in Ytec\Webhook\Api, all wired through etc/di.xml preferences, so behaviour can be replaced without touching the module:
| Interface | Responsibility |
|---|---|
WebhookTriggerHandlerInterface |
Turn an event + entity into webhook messages. |
BodyResolverInterface / BodyParserInterface |
Build the template context for an entity type. |
ConditionEvaluatorInterface / FieldResolverInterface |
Evaluate conditions and resolve field paths. |
AuthorizationApplierInterface |
Apply an authorization scheme to an outgoing request. |
WebhookSenderInterface |
Perform the HTTP request and log the attempt. |
BackoffCalculatorInterface |
Decide whether and when to retry. |
EntityLoaderInterface |
Reload an entity by type and ID at send time. |
DashboardDataProviderInterface |
Feed the health dashboard. |
To support a new entity type, add a BodyParserInterface implementation and register it in the BodyParserFactory DI pool; to support a new event, add an observer that calls WebhookTriggerHandlerInterface::handle() and a case on the WebhookTrigger enum.
License
MIT. See LICENSE.