{"id":280953,"date":"2026-02-16T21:00:04","date_gmt":"2026-02-16T21:00:04","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/webhook-actions\/"},"modified":"2026-05-12T21:07:31","modified_gmt":"2026-05-12T21:07:31","slug":"flowsystems-webhook-actions","status":"publish","type":"plugin","link":"https:\/\/es-cr.wordpress.org\/plugins\/flowsystems-webhook-actions\/","author":23439561,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_crdt_document":"","version":"1.12.1","stable_tag":"1.12.1","tested":"6.9.4","requires":"6.0","requires_php":"8.0","requires_plugins":null,"header_name":"Webhook Actions by Flow Systems","header_author":"Mateusz Skorupa","header_description":"Trigger HTTP webhooks from WordPress actions (do_action). Easily connect WordPress with n8n, Zapier, Make, or custom workflows.","assets_banners_color":"093c2d","last_updated":"2026-05-12 21:07:31","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/wpwebhooks.org\/wordpress-webhook-plugin","header_author_uri":"https:\/\/flowsystems.pl","rating":5,"author_block_rating":0,"active_installs":0,"downloads":1102,"num_ratings":2,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.0":{"tag":"1.0.0","author":"mateuszflowsystems","date":"2026-02-16 21:00:27"},"1.0.1":{"tag":"1.0.1","author":"mateuszflowsystems","date":"2026-02-18 22:35:51"},"1.1.0":{"tag":"1.1.0","author":"mateuszflowsystems","date":"2026-02-28 21:08:40"},"1.1.1":{"tag":"1.1.1","author":"mateuszflowsystems","date":"2026-03-01 12:48:16"},"1.10.0":{"tag":"1.10.0","author":"mateuszflowsystems","date":"2026-05-03 21:47:57"},"1.11.0":{"tag":"1.11.0","author":"mateuszflowsystems","date":"2026-05-06 16:15:26"},"1.12.0":{"tag":"1.12.0","author":"mateuszflowsystems","date":"2026-05-12 20:51:43"},"1.12.1":{"tag":"1.12.1","author":"mateuszflowsystems","date":"2026-05-12 21:07:31"},"1.2.0":{"tag":"1.2.0","author":"mateuszflowsystems","date":"2026-03-07 20:42:42"},"1.2.1":{"tag":"1.2.1","author":"mateuszflowsystems","date":"2026-03-07 21:21:55"},"1.3.0":{"tag":"1.3.0","author":"mateuszflowsystems","date":"2026-03-15 21:22:43"},"1.3.1":{"tag":"1.3.1","author":"mateuszflowsystems","date":"2026-03-15 21:56:44"},"1.3.2":{"tag":"1.3.2","author":"mateuszflowsystems","date":"2026-03-15 22:36:07"},"1.4.0":{"tag":"1.4.0","author":"mateuszflowsystems","date":"2026-03-22 21:17:47"},"1.5.0":{"tag":"1.5.0","author":"mateuszflowsystems","date":"2026-03-23 16:43:47"},"1.6.0":{"tag":"1.6.0","author":"mateuszflowsystems","date":"2026-03-28 22:56:32"},"1.6.1":{"tag":"1.6.1","author":"mateuszflowsystems","date":"2026-03-28 23:00:04"},"1.6.2":{"tag":"1.6.2","author":"mateuszflowsystems","date":"2026-04-05 13:22:48"},"1.7.0":{"tag":"1.7.0","author":"mateuszflowsystems","date":"2026-04-27 17:44:53"},"1.8.0":{"tag":"1.8.0","author":"mateuszflowsystems","date":"2026-04-27 22:49:00"},"1.9.0":{"tag":"1.9.0","author":"mateuszflowsystems","date":"2026-05-03 15:18:40"}},"upgrade_notice":{"1.11.0":"<p>Adds two new condition operators (<code>array_contains<\/code>, <code>object_contains<\/code>) and a <code>stringify<\/code> type cast for matching complex array and object fields. Improves the conditions editor layout for mobile screens. No database changes \u2014 no manual steps required.<\/p>","1.10.0":"<p>Adds per-webhook synchronous execution, delivery log details improvements (request headers, query parameters, collapsible payloads), a fix for GET\/DELETE webhooks not sending custom headers or URL params, and replay support for condition-skipped log entries. Database migration runs automatically \u2014 adds <code>is_synchronous<\/code> to the webhooks table. No manual steps required.<\/p>","1.9.0":"<p>Adds configurable HTTP method, custom headers, and URL query parameters per webhook. Database migration runs automatically \u2014 adds <code>http_method<\/code>, <code>custom_headers<\/code>, <code>url_params<\/code> to the webhooks table and <code>request_headers<\/code>, <code>request_url<\/code> to the logs table. No manual steps required.<\/p>","1.8.0":"<p>Adds type casting in Conditions and Payload Mapping \u2014 cast string values to number, string, or boolean before comparison or before sending to an API. Fixes test webhook result labels to correctly reflect HTTP status codes. No database changes.<\/p>","1.7.0":"<p>Adds test webhook delivery (run-now or via queue without triggering real events) and conditional dispatch \u2014 filter webhooks by payload field values before they fire. No database changes.<\/p>","1.6.2":"<p>Bug fixes: graceful 409 handling for already-completed queue jobs, and dot-containing keys (e.g. Gravity Forms <code>6.1<\/code>) in the mapping editor. No database changes.<\/p>","1.6.1":"<p>Fixes schema API endpoints for slash-based trigger names (e.g. IvyForms hooks) on Apache hosting. No database changes.<\/p>","1.6.0":"<p>Adds built-in IvyForms webhook integration and fixes hook discovery for triggers containing forward slashes. No database changes.<\/p>","1.5.0":"<p>Adds built-in Contact Form 7 webhook integration.\nYou can now send CF7 form submissions to external APIs, automation tools, or CRMs with retry, queue, and replay support \u2014 no additional plugins or custom code required.<\/p>","1.4.0":"<p>Adds Action Scheduler support for significantly more reliable and scalable webhook processing.\nIf Action Scheduler is available (e.g. via WooCommerce), the plugin automatically switches from WP-Cron \u2014 no reactivation or setup required.<\/p>","1.3.0":"<p>Adds a new database table for API tokens. The table is created automatically on update \u2014 no manual steps needed.<\/p>","1.1.1":"<p>Fixes permanently_failed entries being undercounted in delivery statistics. No database changes.<\/p>","1.1.0":"<p>This release adds new database columns (<code>event_uuid<\/code>, <code>event_timestamp<\/code>, <code>attempt_history<\/code>, <code>next_attempt_at<\/code> on logs; <code>log_id<\/code> on queue). The migration runs automatically on plugin activation or update. No manual steps required.<\/p>","1.0.0 \u2014 2026-02-16":"<p>Initial stable release.<\/p>"},"ratings":{"1":0,"2":0,"3":0,"4":0,"5":2},"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3462891,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3462891,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256},"icon.svg":{"filename":"icon.svg","revision":3462891,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3462891,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3462891,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{"blueprint.json":{"filename":"blueprint.json","revision":3530368,"resolution":false,"location":"assets","locale":"","contents":"{\"landingPage\":\"\\\/wp-admin\\\/admin.php?page=fswa-webhook-actions#\\\/webhooks\",\"preferredVersions\":{\"php\":\"8.2\",\"wp\":\"latest\"},\"features\":{\"networking\":true},\"steps\":[{\"step\":\"installPlugin\",\"pluginData\":{\"resource\":\"wordpress.org\\\/plugins\",\"slug\":\"flowsystems-webhook-actions\"},\"options\":{\"activate\":true}},{\"step\":\"activatePlugin\",\"pluginPath\":\"flowsystems-webhook-actions\\\/flowsystems-webhook-actions.php\"},{\"step\":\"login\",\"username\":\"admin\",\"password\":\"password\"}]}"}},"all_blocks":[],"tagged_versions":["1.0.0","1.0.1","1.1.0","1.1.1","1.10.0","1.11.0","1.12.0","1.12.1","1.2.0","1.2.1","1.3.0","1.3.1","1.3.2","1.4.0","1.5.0","1.6.0","1.6.1","1.6.2","1.7.0","1.8.0","1.9.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3521788,"resolution":"1","location":"assets","locale":"","width":1639,"height":862},"screenshot-10.png":{"filename":"screenshot-10.png","revision":3521551,"resolution":"10","location":"assets","locale":"","width":822,"height":925},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3521551,"resolution":"2","location":"assets","locale":"","width":841,"height":902},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3521788,"resolution":"3","location":"assets","locale":"","width":824,"height":900},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3521551,"resolution":"4","location":"assets","locale":"","width":822,"height":888},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3521551,"resolution":"5","location":"assets","locale":"","width":713,"height":874},"screenshot-6.png":{"filename":"screenshot-6.png","revision":3521551,"resolution":"6","location":"assets","locale":"","width":1635,"height":840},"screenshot-7.png":{"filename":"screenshot-7.png","revision":3521551,"resolution":"7","location":"assets","locale":"","width":1635,"height":840},"screenshot-8.png":{"filename":"screenshot-8.png","revision":3483307,"resolution":"8","location":"assets","locale":"","width":1650,"height":765},"screenshot-9.png":{"filename":"screenshot-9.png","revision":3521551,"resolution":"9","location":"assets","locale":"","width":794,"height":785}},"screenshots":{"1":"Webhooks list view","2":"Webhook configuration screen","3":"Selecting WordPress action triggers","4":"Payload mapping configuration","5":"Webhook delivery logs with replay and retry controls","6":"Queue status overview","7":"Settings configuration screen","8":"REST API Tokens configuration screen","9":"Conditional webhook dispatch \u2014 conditions editor","10":"Test webhook drawer \u2014 send a test delivery and inspect request details inline"},"jetpack_post_was_ever_published":false},"plugin_section":[],"plugin_tags":[1556,569,597,243637,34953],"plugin_category":[45],"plugin_contributors":[255989],"plugin_business_model":[],"class_list":["post-280953","plugin","type-plugin","status-publish","hentry","plugin_tags-api","plugin_tags-automation","plugin_tags-integration","plugin_tags-n8n","plugin_tags-webhooks","plugin_category-ecommerce","plugin_contributors-mateuszflowsystems","plugin_committers-mateuszflowsystems"],"banners":{"banner":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/banner-772x250.png?rev=3462891","banner_2x":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/banner-1544x500.png?rev=3462891","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/icon.svg?rev=3462891","icon":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/icon.svg?rev=3462891","icon_2x":false,"generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-1.png?rev=3521788","caption":"Webhooks list view"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-2.png?rev=3521551","caption":"Webhook configuration screen"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-3.png?rev=3521788","caption":"Selecting WordPress action triggers"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-4.png?rev=3521551","caption":"Payload mapping configuration"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-5.png?rev=3521551","caption":"Webhook delivery logs with replay and retry controls"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-6.png?rev=3521551","caption":"Queue status overview"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-7.png?rev=3521551","caption":"Settings configuration screen"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-8.png?rev=3483307","caption":"REST API Tokens configuration screen"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-9.png?rev=3521551","caption":"Conditional webhook dispatch \u2014 conditions editor"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-10.png?rev=3521551","caption":"Test webhook drawer \u2014 send a test delivery and inspect request details inline"}],"raw_content":"<!--section=description-->\n<p>Most WordPress integrations are glue code. A <code>wp_remote_post()<\/code> here, a custom plugin there, an Action Scheduler job nobody else on the team understands. Webhook Actions by Flow Systems replaces that pile with a single, configurable event layer \u2014 so you can ship integrations at the pace the rest of your stack moves.<\/p>\n\n<p>Any <code>do_action<\/code> becomes a first-class API event: queued, retried, logged, replayable, and reachable over a token-authenticated REST API. The WooCommerce-to-HubSpot round-trip that used to be a two-week project is now an afternoon of configuration. The \"can n8n pick this up?\" question gets a yes before the meeting ends.<\/p>\n\n<ul>\n<li><strong>Ship full CRM and SaaS integrations in an afternoon.<\/strong> Two webhooks and a dynamic URL template (<code>https:\/\/api.hubapi.com\/crm\/objects\/2026-03\/deals\/{{ _hs_deal_id }}<\/code>) turn a WooCommerce order lifecycle into a HubSpot deal lifecycle. Same pattern for Pipedrive, Notion, Airtable, internal services \u2014 any REST API that puts ids in the URL path. <strong>(Pro)<\/strong> for the <code>{{ }}<\/code> template syntax, or use the <code>fswa_webhook_url<\/code> filter on the free plan to rewrite the URL from PHP.<\/li>\n<li><strong>Speak n8n, Make, Zapier, and AI-agent fluent.<\/strong> Send any WordPress event into n8n; pull a Claude Code or Cursor agent into your wp-admin via scoped API tokens and let it inspect logs, retry deliveries, and toggle integrations during deploys \u2014 without ever touching WordPress credentials.<\/li>\n<li><strong>Operate WordPress like a real backend.<\/strong> Every event has a UUID, a full request\/response log, a replay button, and an HTTP-addressable REST endpoint. Conditional dispatch, custom headers, query parameters, all five HTTP methods, dynamic URL templates \u2014 match exactly what each external API expects.<\/li>\n<li><strong>Replace expensive automation subscriptions with code you own.<\/strong> Move the Zapier\/Make tasks that bill per-run back into WordPress. Same triggers, same destinations, no per-zap pricing, no third-party data hop.<\/li>\n<li><strong>Let the rest of the team ship without asking for help.<\/strong> Junior devs and ops folks configure webhooks in the admin panel. Filters and a <strong>(Pro)<\/strong> Code Glue snippet system are there when something custom is genuinely needed. The integrations stay readable, observable, and yours.<\/li>\n<\/ul>\n\n<p>Backed by a dedicated persistent queue with intelligent retry and exponential backoff \u2014 the queue lives in your database, under your control. Action Scheduler is auto-detected as the optional trigger (the same job runner WooCommerce uses for production stores) and gracefully falls back to WP-Cron when it isn't available. Same plugin scales from a Contact Form 7 lead form to a high-traffic Black Friday store without changing a line.<\/p>\n\n<h4>The integration architect's toolkit<\/h4>\n\n<p>You already think in events, payloads, idempotency, and observability. Webhook Actions by Flow Systems is the kit that matches: a persistent queue, a full delivery log, replay, scoped REST API tokens, an extensible filter and action surface (<code>fswa_webhook_payload<\/code>, <code>fswa_webhook_url<\/code>, <code>fswa_glue_post_dispatch<\/code> and more), and an event surface AI agents can safely talk to. Plug it in, and the WordPress side of your stack starts to look like the rest of it.<\/p>\n\n<p>\ud83d\udc49 Step-by-step example: <a href=\"https:\/\/wpwebhooks.org\/examples\/cf7-to-webhook\/\">Send Contact Form 7 submissions to a webhook (n8n demo)<\/a>\n\ud83d\udc49 Step-by-step example: <a href=\"https:\/\/wpwebhooks.org\/examples\/gravity-forms-webhooks\/\">Send Gravity Forms Submissions to n8n<\/a>\n\ud83d\udc49 Step-by-step example: <a href=\"https:\/\/wpwebhooks.org\/examples\/ivyforms-to-webhook\/\">Send IvyForms submissions to a webhook (n8n demo)<\/a>\n\ud83d\udc49 Step-by-step example: <a href=\"https:\/\/wpwebhooks.org\/examples\/woocommerce-order-webhook-claude-code\/\">Send WooCommerce orders to n8n on completion, only when the total is over $999 \u2014 wired up with a Claude Code agent<\/a><\/p>\n\n<h4>\u26a1 Webhook Actions Pro<\/h4>\n\n<p>Unlock unlimited conditions, per-webhook retry and backoff settings, type casting in payload mapping, and more.<\/p>\n\n<p><a href=\"https:\/\/wpwebhooks.org\/pricing\/\">See pricing and upgrade \u2192<\/a><\/p>\n\n<h4>Typical Use Cases<\/h4>\n\n<ul>\n<li>CF7 to Webhook: Send Contact Form 7 Data to n8n or external APIs<\/li>\n<li>Gravity Forms webhooks for sending submission to CRM<\/li>\n<li>Send IvyForms submissions to n8n or external APIs<\/li>\n<li>Build reliable form-to-CRM integrations with retry protection<\/li>\n<li>Process high-volume WooCommerce webhooks using Action Scheduler<\/li>\n<li>Send WooCommerce orders to n8n with retry protection<\/li>\n<li>Sync WordPress users to external CRMs safely<\/li>\n<li>Trigger backend microservices from WP hooks<\/li>\n<li>Send event-driven data to internal APIs<\/li>\n<li>Replace fragile custom <code>wp_remote_post()<\/code> integrations<\/li>\n<li>Build idempotent WordPress automation pipelines<\/li>\n<li>Sync WooCommerce orders to HubSpot CRM, Pipedrive, or any REST API \u2014 create a deal on payment, then PATCH the same deal when the order completes, by storing the remote ID locally and replaying it into the URL on later events<\/li>\n<li>Dynamic per-event endpoints \u2014 point a webhook at <code>https:\/\/api.example.com\/resources\/{{ resource_id }}<\/code> and the URL is resolved at dispatch time against the live payload<\/li>\n<li>Query delivery logs, trigger retries, or manage webhooks programmatically from CI\/CD pipelines or external dashboards using API tokens<\/li>\n<li>Allow AI coding assistants (e.g. Claude Code) to inspect webhook logs and retry failed events automatically<\/li>\n<li>Use AI agents to monitor webhook delivery health and operate the queue through the REST API<\/li>\n<\/ul>\n\n<h4>Event Identity &amp; Idempotency<\/h4>\n\n<p>Every dispatched webhook includes:<\/p>\n\n<ul>\n<li>Unique UUID (v4) per event<\/li>\n<li>ISO 8601 UTC timestamp<\/li>\n<li>Embedded <code>event.id<\/code>, <code>event.timestamp<\/code>, <code>event.version<\/code> in the payload<\/li>\n<li><p>HTTP headers: <code>X-Event-Id<\/code>, <code>X-Event-Timestamp<\/code>, <code>X-Webhook-Id<\/code><\/p>\n\n<p>X-Webhook-Id carries the webhook's own stable UUID \u2014 distinct from the per-event <code>X-Event-Id<\/code>. When multiple webhooks point to the same endpoint, the receiving system can use <code>X-Webhook-Id<\/code> to identify which webhook configuration triggered the delivery without inspecting the payload.<\/p><\/li>\n<\/ul>\n\n<p>This enables downstream deduplication, idempotent workflow design, and reliable debugging across systems.<\/p>\n\n<h4>Reliable Queue &amp; Smart Retry<\/h4>\n\n<p>Webhooks are never sent directly from request execution. Instead:<\/p>\n\n<ul>\n<li>Events are stored in a persistent database queue<\/li>\n<li>Processed asynchronously via background jobs<\/li>\n<li>Dispatched in batches to avoid performance impact<\/li>\n<\/ul>\n\n<p>Smart retry routing:<\/p>\n\n<ul>\n<li>5xx and 429 responses \u2192 automatic exponential backoff retry<\/li>\n<li>4xx and 3xx responses \u2192 immediately marked as <code>permanently_failed<\/code><\/li>\n<li>Configurable maximum retry attempts<\/li>\n<li>Full attempt history stored per event<\/li>\n<\/ul>\n\n<p>No silent failures.<\/p>\n\n<h4>Replay Webhook Events<\/h4>\n\n<p>Webhook debugging is difficult when events cannot be reproduced.<\/p>\n\n<p>Webhook Actions by Flow Systems allows you to replay any webhook event directly from the delivery logs \u2014 including successful deliveries and condition-skipped events.<\/p>\n\n<p>This makes it easy to:<\/p>\n\n<ul>\n<li>Re-run automation workflows<\/li>\n<li>Debug external integrations<\/li>\n<li>Recover from temporary endpoint failures<\/li>\n<li>Test webhook consumers without recreating WordPress events<\/li>\n<li>Re-evaluate previously skipped events after changing webhook conditions<\/li>\n<\/ul>\n\n<p>Each replay uses the original payload and event metadata, ensuring consistent behavior across retries and debugging sessions.<\/p>\n\n<h4>Conditional Dispatch<\/h4>\n\n<p>Not every WordPress event should trigger a webhook. Conditional dispatch lets you define field-level rules on any webhook \u2014 the event is only delivered if the conditions pass. Events that fail the check are logged with a <code>skipped<\/code> status and can be replayed later after adjusting the conditions.<\/p>\n\n<p>Conditions are evaluated using dot-notation field paths. Each condition specifies a field, an optional type cast, an operator, and a comparison value. The field selector shows the live captured payload so you can click through nested structures and pick the exact path without typing it manually.<\/p>\n\n<p><strong>Evaluate against original or transformed payload<\/strong><\/p>\n\n<p>Each trigger schema exposes a toggle to choose which payload conditions are evaluated against:<\/p>\n\n<ul>\n<li><strong>Original<\/strong> \u2014 the pre-mapping payload exactly as the WordPress hook fired (default for most use cases). Use this to filter on raw hook arguments like the new WooCommerce status in <code>args.2<\/code>.<\/li>\n<li><strong>Transformed<\/strong> \u2014 the post-mapping, post-enrichment payload that will actually be sent. Use this to filter on fields injected by <code>fswa_payload<\/code>, <code>fswa_webhook_payload<\/code>, or <strong>(Pro)<\/strong> Code Glue snippets \u2014 for example, dispatch only when a remote id was successfully resolved.<\/li>\n<\/ul>\n\n<p><strong>Operators include:<\/strong> equals, not equals, contains, starts with, ends with, is empty, has value, greater than, less than, <code>array_contains<\/code>, <code>object_contains<\/code><\/p>\n\n<p><strong>Type casting before comparison:<\/strong> auto-detect, number, string, boolean, or <code>stringify<\/code> (JSON-encodes arrays and objects into a string for pattern matching)<\/p>\n\n<p><strong>Example \u2014 WooCommerce: fire only when a specific product is in the order<\/strong><\/p>\n\n<p>A <code>woocommerce_order_status_changed<\/code> hook passes the full order object. The payload includes <code>args.1.line_items<\/code> \u2014 an array of purchased products, each with fields like <code>product_id<\/code>, <code>quantity<\/code>, and <code>subtotal<\/code>. To send a webhook only when product ID 26 appears in the order:<\/p>\n\n<ul>\n<li>Field: <code>args.1.line_items<\/code><\/li>\n<li>Operator: <code>has value<\/code> \u2192 key: <code>product_id<\/code>, value: <code>26<\/code><\/li>\n<\/ul>\n\n<p>The webhook stays silent for every other order and fires only when that product is purchased. No custom PHP, no extra filters \u2014 just a condition rule configured in the admin panel.<\/p>\n\n<p>The same pattern works for any hook-based event: filter by post type, form field value, user role, order total, or any other field present in the payload.<\/p>\n\n<p>Free plan includes one condition with AND matching. <a href=\"https:\/\/wpwebhooks.org\/pricing\/\">Upgrade to Pro<\/a> for unlimited conditions, multiple condition groups with independent AND\/OR logic per group, and ANY (OR) matching.<\/p>\n\n<h4>Synchronous Execution<\/h4>\n\n<p>By default, all webhooks are delivered asynchronously via the built-in queue \u2014 events are stored, processed in the background, and retried automatically on failure. This is the recommended approach for production sites.<\/p>\n\n<p>For specific webhooks that require inline delivery (e.g. an internal API that must respond within the same request), you can enable <strong>Synchronous Execution<\/strong> per webhook:<\/p>\n\n<ul>\n<li>The webhook fires during the WordPress request that triggered it \u2014 no queue delay<\/li>\n<li>The first attempt runs blocking in the current request<\/li>\n<li>If that attempt fails with a retryable error (5xx, transport error), it automatically falls back to the queue with standard exponential backoff starting at attempt 2<\/li>\n<li>Non-retryable failures (4xx) are marked permanently failed immediately<\/li>\n<li>A warning dialog must be acknowledged before enabling, and can be dismissed permanently per-browser<\/li>\n<\/ul>\n\n<p>Use with caution on user-facing requests \u2014 a slow or unreachable endpoint will delay page loads, form submissions, and other frontend interactions.<\/p>\n\n<h4>Delivery Observability<\/h4>\n\n<p>Operational visibility built into the admin panel:<\/p>\n\n<p>Status states: <code>pending<\/code>, <code>processing<\/code>, <code>success<\/code>, <code>failed<\/code> (retrying), <code>permanently_failed<\/code><\/p>\n\n<ul>\n<li>Attempt timeline per event<\/li>\n<li>HTTP status codes and response bodies<\/li>\n<li>Inspect full request payloads<\/li>\n<li>Manual retry (single or bulk)<\/li>\n<li>Replay webhook events for debugging and testing integrations<\/li>\n<\/ul>\n\n<p>Filter by: event UUID, target URL, date range, status<\/p>\n\n<p>Queue health metrics:<\/p>\n\n<ul>\n<li>Average attempts per event<\/li>\n<li>Oldest pending job age<\/li>\n<li>Queue stuck detection<\/li>\n<li>WP-Cron-only warning<\/li>\n<\/ul>\n\n<p>Designed as an operations console \u2014 not just a webhook sender.<\/p>\n\n<h4>Payload Mapping<\/h4>\n\n<p>Adapt outgoing JSON payloads to match any external API:<\/p>\n\n<ul>\n<li>Rename fields using dot notation<\/li>\n<li>Restructure nested objects<\/li>\n<li>Exclude sensitive or unnecessary data<\/li>\n<li>Cast field values to number, string, or boolean before sending (e.g. WooCommerce price <code>\"100.50\"<\/code> \u2192 <code>100.5<\/code>)<\/li>\n<li>Store example payloads for configuration<\/li>\n<li>Modify via <code>fswa_payload<\/code> filter<\/li>\n<\/ul>\n\n<p>Payloads always include stable event metadata for consistency.<\/p>\n\n<h4>Configurable HTTP Requests<\/h4>\n\n<p>Every webhook can be configured to match exactly what the target API expects:<\/p>\n\n<p><strong>HTTP Method<\/strong><\/p>\n\n<p>Choose the method used for each delivery: GET, POST, PUT, PATCH, or DELETE. Default is POST.<\/p>\n\n<p><strong>Custom Request Headers<\/strong><\/p>\n\n<p>Add any number of key\/value header pairs sent with every delivery. Header values support dot-notation paths \u2014 reference any field from the outgoing payload directly (e.g. <code>event.id<\/code>, <code>site.url<\/code>). Resolved at dispatch time against the live payload.<\/p>\n\n<p><strong>URL Query Parameters<\/strong><\/p>\n\n<p>Append query parameters to the endpoint URL at dispatch time. Values also support dot-notation payload resolution.<\/p>\n\n<p>For GET and DELETE requests \u2014 where a request body is not appropriate \u2014 query parameters become the primary payload transport. If no params are configured, the full payload is sent as a <code>?payload=<\/code> fallback. POST, PUT, and PATCH send a JSON body as normal; any configured params are appended to the URL in addition.<\/p>\n\n<p><strong>Request details in delivery logs<\/strong><\/p>\n\n<p>Every delivery log stores the exact headers sent and the fully resolved URL (including all query parameters), so you can inspect precisely what was dispatched.<\/p>\n\n<h4>Per-Event Dynamic URLs (Free, via filter)<\/h4>\n\n<p>Many REST APIs require an object id directly in the path \u2014 HubSpot, Pipedrive, Stripe, Notion, custom internal services. The free plugin exposes the <code>fswa_webhook_url<\/code> filter so you can rewrite the endpoint URL per event from PHP, with full access to the outgoing payload, the webhook configuration, the trigger name, and the original pre-mapping payload.<\/p>\n\n<pre><code>add_filter( 'fswa_webhook_url', function ( $url, $payload, $webhook, $trigger, $original ) {\n    if ( (int) $webhook['id'] === 30 ) {\n        $deal_id = $payload['_hs_deal_id'] ?? '';\n        return \"https:\/\/api.hubapi.com\/crm\/objects\/2026-03\/deals\/{$deal_id}\";\n    }\n    return $url;\n}, 10, 5 );\n<\/code><\/pre>\n\n<p>The same filter powers the <strong>(Pro)<\/strong> template syntax described below, so any URL you can build with <code>{{ }}<\/code> placeholders you can also build by hand on the free plan.<\/p>\n\n<h4>Dynamic URL Templates (Pro)<\/h4>\n\n<p><strong>(Pro)<\/strong> Endpoint URLs can contain <code>{{ field.path }}<\/code> placeholders that are resolved per event against the live payload at dispatch time \u2014 no PHP required. Configure entirely from the webhook edit screen.<\/p>\n\n<p><strong>Syntax<\/strong><\/p>\n\n<pre><code>https:\/\/api.hubapi.com\/crm\/objects\/2026-03\/deals\/{{ _hs_deal_id }}\nhttps:\/\/api.example.com\/v1\/resources\/{{ resource_id }}\/notes\nhttps:\/\/api.example.com\/users\/{{ user.id }}\/orders\/{{ order.id }}\n<\/code><\/pre>\n\n<p>Same dot-notation as custom headers and URL parameters. Values are <code>rawurlencode()<\/code>'d before substitution to keep the URL valid.<\/p>\n\n<p><strong>Resolution order<\/strong><\/p>\n\n<p>The template is resolved against the outgoing (post-mapping) payload first. If a placeholder is not found there, the original pre-mapping payload is consulted as a fallback \u2014 so paths from the captured event keep working even after payload mapping renames or removes top-level fields.<\/p>\n\n<p><strong>Example \u2014 WooCommerce \u2192 HubSpot deal update<\/strong><\/p>\n\n<ol>\n<li>On <code>woocommerce_payment_complete<\/code>, send a POST to <code>https:\/\/api.hubapi.com\/crm\/objects\/2026-03\/deals<\/code> to create the deal. Store the returned deal id in the WooCommerce order's post meta.<\/li>\n<li>On <code>woocommerce_order_status_changed<\/code>, configure a second webhook with endpoint URL <code>https:\/\/api.hubapi.com\/crm\/objects\/2026-03\/deals\/{{ _hs_deal_id }}<\/code> and method <code>PATCH<\/code>. Inject <code>_hs_deal_id<\/code> into the payload (read from order meta), and the URL resolves to the right HubSpot deal on every event.<\/li>\n<\/ol>\n\n<p>This pattern works for any REST API that uses resource ids in the URL path. Injecting the id from external storage (post meta, options, transients) can be done with the <code>fswa_webhook_payload<\/code> filter on the free plan, or <strong>(Pro)<\/strong> with no code at all using <a href=\"https:\/\/wpwebhooks.org\/pricing\/\">Webhook Actions Pro Code Glue<\/a>.<\/p>\n\n<h4>REST API Access with Token Authentication<\/h4>\n\n<p>The plugin exposes a full operational REST API (<code>\/wp-json\/fswa\/v1\/<\/code>) that powers the admin interface and can also be used directly by external tools, automation systems, AI agents, and CI\/CD pipelines.<\/p>\n\n<p>Every endpoint supports dual authentication:<\/p>\n\n<ul>\n<li>WordPress admin session (cookie-based, used by the admin panel)<\/li>\n<li>API token \u2014 for programmatic access without a browser session<\/li>\n<\/ul>\n\n<p><strong>API Tokens<\/strong><\/p>\n\n<p>Create tokens directly from the API Tokens screen in the admin panel. Each token is assigned one of three scopes:<\/p>\n\n<ul>\n<li><code>read<\/code> \u2014 GET access to webhooks, logs, queue, health, triggers, and schemas<\/li>\n<li><code>operational<\/code> \u2014 Read + toggle webhooks on\/off, retry and replay log entries, execute queue jobs<\/li>\n<li><code>full<\/code> \u2014 Operational + create, update, and delete webhooks, schemas, and queue jobs<\/li>\n<\/ul>\n\n<p>Token authentication is accepted via:<\/p>\n\n<ul>\n<li><code>X-FSWA-Token: &lt;token&gt;<\/code> header (recommended)<\/li>\n<li><code>Authorization: Bearer &lt;token&gt;<\/code> header<\/li>\n<li><code>?api_token=&lt;token&gt;<\/code> query parameter<\/li>\n<\/ul>\n\n<p>Tokens can be set to expire and rotated at any time. Rotation issues a new secret immediately while preserving the token's name, scope, and settings. Token management always requires a WordPress admin login \u2014 tokens cannot be used to create or manage other tokens.<\/p>\n\n<p>Full REST API documentation: <a href=\"https:\/\/wpwebhooks.org\/webhook-wordpress-plugin-api\/\">REST API Reference<\/a><\/p>\n\n<h4>AI Agents and Programmatic Automation<\/h4>\n\n<p>The REST API makes Webhook Actions by Flow Systems accessible to AI-powered tools and coding agents.<\/p>\n\n<p>Automation systems, CI pipelines, and AI coding assistants  &hellip;<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the plugin files to the <code>\/wp-content\/plugins\/flowsystems-webhook-actions<\/code> directory, or install the plugin through the WordPress plugins screen.<\/li>\n<li>Activate the plugin through the 'Plugins' screen in WordPress.<\/li>\n<li>Navigate to Webhook Actions in the admin menu.<\/li>\n<li>Add your webhook endpoint URL and select the desired WordPress action triggers.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"can%20i%20send%20contact%20form%207%20submissions%20to%20a%20webhook%3F\"><h3>Can I send Contact Form 7 submissions to a webhook?<\/h3><\/dt>\n<dd><p>Yes. When Contact Form 7 is active, form submissions are automatically available as webhook triggers. You can send them to any external API, automation tool (like n8n), or CRM.<\/p><\/dd>\n<dt id=\"do%20i%20need%20an%20extra%20plugin%20for%20cf7%20to%20webhook%3F\"><h3>Do I need an extra plugin for CF7 to webhook?<\/h3><\/dt>\n<dd><p>No. Contact Form 7 integration is built in. No additional plugins or custom code are required.<\/p><\/dd>\n<dt id=\"what%20does%20the%20contact%20form%207%20webhook%20payload%20look%20like%3F\"><h3>What does the Contact Form 7 webhook payload look like?<\/h3><\/dt>\n<dd><p>The plugin normalizes CF7 submissions into a clean JSON structure including form metadata, submitted fields, making it easy to consume in external systems.<\/p><\/dd>\n<dt id=\"can%20this%20handle%20high%20webhook%20volume%3F\"><h3>Can this handle high webhook volume?<\/h3><\/dt>\n<dd><p>Yes. The plugin uses a persistent queue with batch processing and supports Action Scheduler for scalable background execution, making it suitable for high-traffic WordPress and WooCommerce sites.<\/p><\/dd>\n<dt id=\"does%20this%20plugin%20support%20action%20scheduler%3F\"><h3>Does this plugin support Action Scheduler?<\/h3><\/dt>\n<dd><p>Yes. If Action Scheduler is available (for example via WooCommerce), the plugin will automatically use it for queue processing instead of WP-Cron. No configuration or reactivation is required.<\/p><\/dd>\n<dt id=\"what%20is%20the%20difference%20between%20wp-cron%20and%20action%20scheduler%3F\"><h3>What is the difference between WP-Cron and Action Scheduler?<\/h3><\/dt>\n<dd><p>WP-Cron depends on site traffic and can be unreliable on low-traffic sites. Action Scheduler is a dedicated background job system used by WooCommerce that provides more consistent and reliable execution of queued jobs.<\/p>\n\n<p>Webhook Actions by Flow Systems automatically uses Action Scheduler when available.<\/p><\/dd>\n<dt id=\"what%20is%20a%20wordpress%20action%3F\"><h3>What is a WordPress action?<\/h3><\/dt>\n<dd><p>An action is a WordPress hook triggered at a specific moment, such as when a user is created, a post is saved, or an order is completed.<\/p><\/dd>\n<dt id=\"can%20i%20use%20this%20plugin%20with%20n8n%20or%20other%20automation%20tools%3F\"><h3>Can I use this plugin with n8n or other automation tools?<\/h3><\/dt>\n<dd><p>This plugin works seamlessly with n8n webhook triggers and can be used with any automation platform or external API that accepts HTTP webhooks.<\/p><\/dd>\n<dt id=\"does%20this%20plugin%20support%20woocommerce%20hooks%3F\"><h3>Does this plugin support WooCommerce hooks?<\/h3><\/dt>\n<dd><p>Yes. Any WooCommerce action can be used as a trigger, as long as the hook is available.<\/p><\/dd>\n<dt id=\"how%20does%20the%20retry%20mechanism%20work%3F\"><h3>How does the retry mechanism work?<\/h3><\/dt>\n<dd><p>Failed webhooks are automatically retried using exponential backoff. The delay increases with each attempt (e.g., 1 minute, 2 minutes, 4 minutes, 8 minutes), up to a maximum delay of 1 hour between retries. By default, 5 attempts are made before marking a job as failed. The retry behavior can be adjusted using available filters.<\/p><\/dd>\n<dt id=\"can%20i%20replay%20webhook%20events%3F\"><h3>Can I replay webhook events?<\/h3><\/dt>\n<dd><p>Yes. Every webhook delivery is logged with its payload and attempt history.\nYou can replay successful events and condition-skipped events directly from the logs \u2014 useful for debugging integrations, re-running automation workflows, or re-evaluating a skipped event after you've changed the webhook's conditions.<\/p><\/dd>\n<dt id=\"what%20is%20payload%20mapping%3F\"><h3>What is Payload Mapping?<\/h3><\/dt>\n<dd><p>Payload Mapping allows you to transform the webhook payload before it is sent. You can rename fields, reorganize the structure, or exclude sensitive data. The plugin can store example payloads to help configure mappings.<\/p><\/dd>\n<dt id=\"can%20i%20include%20user%20data%20in%20webhook%20payloads%3F\"><h3>Can I include user data in webhook payloads?<\/h3><\/dt>\n<dd><p>Yes. For user-related triggers (such as <code>user_register<\/code>, <code>profile_update<\/code>, <code>wp_login<\/code>, <code>wp_logout<\/code>), you can enable \"Include User Data\" to automatically add user information (ID, email, display name, roles, etc.) to the payload.<\/p><\/dd>\n<dt id=\"can%20i%20access%20the%20rest%20api%20without%20a%20wordpress%20login%3F\"><h3>Can I access the REST API without a WordPress login?<\/h3><\/dt>\n<dd><p>Yes. Create an API token from the API Tokens screen in the admin panel and use it in the <code>X-FSWA-Token<\/code> header (or <code>Authorization: Bearer<\/code>) with your requests. Tokens support three scopes \u2014 <code>read<\/code>, <code>operational<\/code>, and <code>full<\/code> \u2014 so you can grant only the access level each integration needs.<\/p><\/dd>\n<dt id=\"is%20this%20plugin%20free%3F\"><h3>Is this plugin free?<\/h3><\/dt>\n<dd><p>Yes. The core plugin is completely free and licensed under GPL. Webhook Actions Pro is an optional paid upgrade that adds unlimited conditions, per-webhook retry and backoff settings, and more. <a href=\"https:\/\/wpwebhooks.org\/pricing\/\">Learn more \u2192<\/a><\/p><\/dd>\n<dt id=\"can%20i%20use%20conditions%20to%20filter%20which%20webhooks%20fire%3F\"><h3>Can I use conditions to filter which webhooks fire?<\/h3><\/dt>\n<dd><p>Yes. Each webhook can have conditions evaluated against the incoming payload before dispatch. Free plan supports one condition with AND match. Pro plan supports unlimited conditions, condition groups, and AND\/OR logic per group. Each trigger can choose whether conditions evaluate against the original (pre-mapping) payload or the transformed (post-mapping, post-Code-Glue) payload.<\/p><\/dd>\n<dt id=\"can%20the%20endpoint%20url%20change%20per%20event%3F\"><h3>Can the endpoint URL change per event?<\/h3><\/dt>\n<dd><p>Yes. The endpoint URL supports dot-notation placeholders like <code>https:\/\/api.example.com\/v1\/resources\/{{ resource_id }}\/notes<\/code>. Placeholders are resolved at dispatch time against the live payload and <code>rawurlencode()<\/code>'d before substitution. Resolution tries the outgoing (post-mapping) payload first and falls back to the original captured payload, so paths from the captured event keep working even after mapping rewrites top-level fields.<\/p><\/dd>\n<dt id=\"can%20i%20sync%20woocommerce%20orders%20to%20hubspot%2C%20pipedrive%2C%20or%20another%20crm%20that%20requires%20a%20deal%20id%20in%20the%20url%3F\"><h3>Can I sync WooCommerce orders to HubSpot, Pipedrive, or another CRM that requires a deal id in the URL?<\/h3><\/dt>\n<dd><p>Yes. Use two webhooks: the first creates the remote resource on payment completion and stores the returned id in WordPress (e.g. post meta on the order). The second updates the resource on subsequent events using a URL like <code>https:\/\/api.hubapi.com\/crm\/objects\/2026-03\/deals\/{{ _hs_deal_id }}<\/code> and injects the stored id back into the payload. The injection can be done with the <code>fswa_webhook_payload<\/code> filter, or <strong>(Pro)<\/strong> with no code at all using <a href=\"https:\/\/wpwebhooks.org\/pricing\/\">Webhook Actions Pro Code Glue<\/a>.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.12.1 \u2014 2026-05-12<\/h4>\n\n<ul>\n<li>Docs: corrected README to reflect that type casting (in both payload mapping and conditions) is a free-plan feature; removed misleading Pro markers<\/li>\n<li>Docs: added WooCommerce \u2192 n8n step-by-step example showing conditional dispatch wired up via a Claude Code agent<\/li>\n<li>Docs: reformatted <code>fswa_webhook_url<\/code> PHP code sample so it renders as a single code block on the wordpress.org plugin page<\/li>\n<li>No code changes<\/li>\n<\/ul>\n\n<h4>1.12.0 \u2014 2026-05-12<\/h4>\n\n<ul>\n<li>Added <code>fswa_webhook_payload<\/code> filter \u2014 Pro-extensible enrichment of the outgoing payload before dispatch; receives the mapped payload, webhook id, trigger name, and pre-mapping original payload<\/li>\n<li>Added <code>fswa_glue_post_dispatch<\/code> action \u2014 fires after every delivery with response code\/body, mapped payload, webhook, and original pre-mapping payload; intended for Pro post-dispatch snippets<\/li>\n<li>Added <code>fswa_webhook_url<\/code> filter \u2014 Pro-extensible URL template expansion; passes the URL, post-glue payload, webhook, trigger, and original pre-mapping payload as fallback for dot-notation token resolution<\/li>\n<li>Added per-trigger <code>conditions_evaluate_on<\/code> setting (original \/ transformed) \u2014 choose whether conditions evaluate against the pre-mapping payload or the post-mapping\/post-glue payload; segmented toggle in the trigger schema panel<\/li>\n<li>Added dual-resolution for custom headers and URL parameters \u2014 when a dot-notation path resolves to null in the post-glue payload, falls back to the pre-mapping original payload; matches existing condition resolution semantics<\/li>\n<li>Added collapsible Original Payload section in the Mapping editor \u2014 inspect the pre-glue\/pre-mapping payload while authoring field mappings<\/li>\n<li>Improved <code>object_contains<\/code> operator \u2014 also matches when the value is present at the current array level, not only nested; works for array-typed WooCommerce fields like <code>meta_data<\/code> and <code>line_items<\/code><\/li>\n<li>Improved delivery log writes \u2014 <code>request_payload<\/code>, <code>original_payload<\/code>, and <code>mapping_applied<\/code> are now refreshed after the <code>fswa_webhook_payload<\/code> filter mutates the payload<\/li>\n<li>Fixed condition evaluation order \u2014 re-evaluates conditions after Code Glue pre-dispatch when <code>conditions_evaluate_on<\/code> is <code>transformed<\/code><\/li>\n<li>Fixed pre-glue filter application in synchronous mode \u2014 applied exactly once during the inline attempt instead of once before enqueue and again at send<\/li>\n<\/ul>\n\n<h4>1.11.0 \u2014 2026-05-06<\/h4>\n\n<ul>\n<li>Added <code>array_contains<\/code> condition operator \u2014 checks whether an array field contains a specified value; works with flat arrays and arrays of objects<\/li>\n<li>Added <code>object_contains<\/code> condition operator \u2014 checks whether an object field contains a specified key (optionally filtered to a specific property within nested objects using a <code>key=<\/code> parameter)<\/li>\n<li>Added <code>stringify<\/code> type cast \u2014 JSON-encodes array and object field values into a string before comparison, enabling string-based operators on complex nested structures<\/li>\n<li>Improved FieldSelector \u2014 split navigate and select actions; added a dedicated \"+\" button to select non-leaf fields (arrays and objects) directly without drilling further into children<\/li>\n<li>Improved ConditionsEditor layout \u2014 responsive three-row design on small screens (field + delete on top row, cast + operator on second row, value on third); <code>object_contains<\/code> exposes an inline property name input when a key filter is needed<\/li>\n<li>Delivery log detail messages now include the property key when <code>object_contains<\/code> is matched against a specific property<\/li>\n<\/ul>\n\n<h4>1.10.0 \u2014 2026-05-03<\/h4>\n\n<ul>\n<li>Added per-webhook synchronous execution mode \u2014 when enabled, the webhook fires inline during the WordPress request that triggers it, bypassing the queue; a warning dialog explains the performance impact before enabling; dismissal can be stored permanently per-browser<\/li>\n<li>First synchronous attempt runs blocking in the current request; retryable failures (5xx, transport errors) automatically fall back to the async queue starting at attempt 2 with standard exponential backoff; non-retryable failures (4xx) are marked permanently failed immediately<\/li>\n<li>Added sync execution toggle to the Webhooks list view \u2014 enable or disable per webhook without opening the edit screen<\/li>\n<li>Added Request Headers and Query Parameters sections to delivery log details \u2014 inspect the exact headers and URL parameters sent with each delivery<\/li>\n<li>Added collapsible Request Payload and Original Payload sections in delivery log details \u2014 collapse state is persisted in browser storage so the panel opens in the same state on next visit<\/li>\n<li>Fixed GET and DELETE webhooks not including custom headers or URL parameters in deliveries<\/li>\n<li>Added replay support for skipped (condition-failed) log entries \u2014 re-evaluate a previously skipped event after changing the webhook's conditions<\/li>\n<\/ul>\n\n<h4>1.9.0 \u2014 2026-05-03<\/h4>\n\n<ul>\n<li>Added configurable HTTP method per webhook \u2014 choose GET, POST, PUT, PATCH, or DELETE (default: POST)<\/li>\n<li>Added custom request headers per webhook \u2014 define key\/value pairs sent with every delivery; values support dot-notation paths resolved against the outgoing payload<\/li>\n<li>Added URL query parameters per webhook \u2014 appended to the endpoint URL; for GET and DELETE requests, query params are the primary payload transport (no body); a full <code>?payload=<\/code> fallback is used when no params are configured<\/li>\n<li>Added <code>fswa_capture_payload<\/code> filter \u2014 modify or enrich the payload stored as the captured example without affecting what is dispatched; designed for Pro extensions and custom PHP snippets<\/li>\n<li>Added <code>fswa_webhook_response<\/code> action \u2014 fires after every HTTP response is received per webhook; intended for Pro extensions to run custom logic against the response (parse body, trigger follow-up actions, store data)<\/li>\n<li>Added <code>request_headers<\/code> and <code>request_url<\/code> columns to delivery logs \u2014 the exact headers sent and the fully resolved URL (with query params applied) are now stored and visible in the delivery log<\/li>\n<li>Improved test webhook drawer \u2014 defaults to \"Captured + Mapping\" payload source; result panel now shows HTTP method, fully resolved endpoint URL, sent headers, and request body<\/li>\n<\/ul>\n\n<h4>1.8.0 \u2014 2026-04-28<\/h4>\n\n<ul>\n<li>Added type casting in Conditions \u2014 cast field values to number, string, or boolean before comparison; enables greater than \/ less than on numeric strings (e.g. WooCommerce price \"100.50\")<\/li>\n<li>Added type casting in Payload Mapping \u2014 cast field values before sending to external APIs<\/li>\n<li>Added <code>X-Webhook-Id<\/code> request header \u2014 sent with every delivery; carries the webhook's stable UUID so downstream systems can identify which webhook configuration triggered the request when multiple webhooks share the same endpoint<\/li>\n<li>Fixed test webhook result label \u2014 now reflects actual HTTP status: 2xx = Success, 3xx = Redirect, 4xx = Client Error, 5xx = Server Error (previously all completed deliveries showed green \"Success\")<\/li>\n<\/ul>\n\n<h4>1.7.0 \u2014 2026-04-27<\/h4>\n\n<ul>\n<li>Added \"Test Webhook\" delivery with run-now and queue modes \u2014 test webhook delivery without triggering real WordPress events<\/li>\n<li>Added conditional webhook dispatch \u2014 filter events by payload field values before dispatch; free plan includes one condition with AND match<\/li>\n<li>Added field selector with live preview in the Conditions editor to build conditions from real example payloads<\/li>\n<li>Fixed attempt history timestamps displayed in browser local time<\/li>\n<li>Renamed plugin to \"Webhook Actions by Flow Systems\"<\/li>\n<li>Added Webhook Actions Pro integration tab for license management<\/li>\n<\/ul>\n\n<h4>1.6.2 \u2014 2026-04-05<\/h4>\n\n<ul>\n<li>Fixed graceful handling of 409 responses when a queue job was already completed in a background process<\/li>\n<li>Fixed mapping editor not supporting dot-containing keys (e.g. Gravity Forms sub-field IDs like <code>6.1<\/code>)<\/li>\n<\/ul>\n\n<h4>1.6.1 \u2014 2026-03-28<\/h4>\n\n<ul>\n<li>Fixed schema API endpoints for triggers containing forward slashes (e.g. <code>ivyforms\/form\/before_submission<\/code>) returning 404 on Apache \u2014 admin now uses double-encoding to pass through Apache's encoded-slash restriction<\/li>\n<\/ul>\n\n<h4>1.6.0 \u2014 2026-03-28<\/h4>\n\n<ul>\n<li>Added built-in IvyForms integration \u2014 automatically normalizes IvyForms field objects and enriches submission payloads for <code>ivyforms\/form\/before_submission<\/code> and <code>ivyforms\/form\/after_submission<\/code> hooks<\/li>\n<li>Added IntegrationLoader to centralize third-party integration registration<\/li>\n<li>Fixed forward slashes not being recognized in hook names during dynamic trigger discovery<\/li>\n<li>Fixed percent-encoded slashes in schemas REST route trigger param not being decoded correctly<\/li>\n<li>Fixed trigger name not being URL-encoded when building schemas API requests from the admin UI<\/li>\n<\/ul>\n\n<h4>1.5.0 \u2014 2026-03-23<\/h4>\n\n<ul>\n<li>Added built-in CF7 to webhook integration \u2014 automatically sends CF7 submissions as structured webhook payloads (form id, title, fields, meta, uploaded files)<\/li>\n<li>Added <code>fswa_normalize_object<\/code> filter for custom third-party object normalization<\/li>\n<li>Added <code>get_properties()<\/code> fallback in payload normalization to handle objects with private or protected properties<\/li>\n<li>Improved hook registration to capture all hook arguments by default (PHP_INT_MAX accepted_args)<\/li>\n<\/ul>\n\n<h4>1.4.0 \u2014 2026-03-22<\/h4>\n\n<ul>\n<li>Added Action Scheduler support for queue processing (auto-detected, no configuration required)<\/li>\n<li>Automatic migration from WP-Cron to Action Scheduler when available<\/li>\n<li>Added option to move admin menu under Tools for cleaner dashboard navigation<\/li>\n<li>Added dynamic trigger discovery via static PHP source scan<\/li>\n<li>Reduced triggers API responses size<\/li>\n<li>Fixed input focus styles in admin forms<\/li>\n<\/ul>\n\n<h4>1.3.2 \u2014 2026-03-15<\/h4>\n\n<ul>\n<li>Fixed <code>auth_header<\/code> field being exposed to API tokens without <code>full<\/code> scope \u2014 read and operational tokens now receive a permission notice instead<\/li>\n<\/ul>\n\n<h4>1.3.1 \u2014 2026-03-15<\/h4>\n\n<ul>\n<li>Fixed log details dialog showing error message from the first attempt instead of the most recent one<\/li>\n<li>Added <a href=\"https:\/\/wpwebhooks.org\/webhook-wordpress-plugin-api\/\">REST API Reference<\/a> link to the plugin description<\/li>\n<\/ul>\n\n<h4>1.3.0 \u2014 2026-03-15<\/h4>\n\n<ul>\n<li>Added API token authentication for the REST API \u2014 create tokens with <code>read<\/code>, <code>operational<\/code>, or <code>full<\/code> scope; tokens are SHA-256 hashed at rest and accepted via <code>X-FSWA-Token<\/code> header, <code>Authorization: Bearer<\/code>, or <code>?api_token=<\/code> query param<\/li>\n<li>Added token expiry support with optional <code>expires_at<\/code>; expired tokens are rejected at auth time and visually flagged in the admin panel<\/li>\n<li>Added token rotation \u2014 issues a new secret while preserving all other token fields; optionally updates expiry in the same request; revived expired tokens auto-extend to +30 days by default<\/li>\n<li>Added <code>PATCH \/tokens\/{id}<\/code> endpoint for updating <code>expires_at<\/code> independently of rotation<\/li>\n<li>Added <code>fswa_api_tokens<\/code> database table (migration 1.3.0)<\/li>\n<li>Applied scope-based dual auth (<code>manage_options<\/code> session OR valid token) to all existing REST controllers: <code>read<\/code> for GET endpoints, <code>operational<\/code> for toggle\/retry\/replay, <code>full<\/code> for create\/update\/delete<\/li>\n<li>Fixed all admin UI date displays (logs, queue, schema panel) to show times in the user's local timezone instead of raw UTC<\/li>\n<li>Fixed date range filters (logs, queue) to correctly convert local picker values to UTC before querying<\/li>\n<li>Improved log details panel \u2014 error message, response body, HTTP code, and duration now reflect the most recent attempt history entry rather than the top-level log fields<\/li>\n<\/ul>\n\n<h4>1.2.1 \u2014 2026-03-07<\/h4>\n\n<ul>\n<li>Fixed retry returning 500 when a log has multiple queue jobs (replay + original) \u2014 <code>findByLogId<\/code> now returns the most recent job via <code>ORDER BY id DESC<\/code><\/li>\n<li>Fixed <code>forceRetry<\/code> rejecting jobs with status <code>failed<\/code> \u2014 restored <code>failed<\/code> to the allowed status list alongside <code>pending<\/code> and <code>permanently_failed<\/code><\/li>\n<\/ul>\n\n<h4>1.2.0 \u2014 2026-03-07<\/h4>\n\n<ul>\n<li>Added persistent delivery stats table (<code>fswa_stats<\/code>) for long-term aggregation<\/li>\n<li>Added replay button for successful log entries<\/li>\n<li>Added \"Execute Now\" button in replay dialog with auto-open log details<\/li>\n<li>Added full attempt history with response body, accordion UI, and next attempt countdown<\/li>\n<li>Replaced browser <code>confirm()<\/code> dialogs with modal confirmations<\/li>\n<li>Fixed queue stats \u2014 removed stale <code>failed<\/code> status, added <code>permanently_failed<\/code><\/li>\n<li>Fixed retry eligibility check to use log status instead of queue job status<\/li>\n<li>Fixed \"Execute Now\" button visibility to only show for pending jobs<\/li>\n<\/ul>\n\n<h4>1.1.1 \u2014 2026-03-01<\/h4>\n\n<ul>\n<li>Fixed <code>permanently_failed<\/code> entries being excluded from total and error delivery statistics in <code>getStats()<\/code>, <code>getAllTimeStats()<\/code>, and <code>LogArchiver::aggregateStatsBeforeDeletion()<\/code><\/li>\n<\/ul>\n\n<h4>1.1.0 \u2014 2026-02-28<\/h4>\n\n<ul>\n<li>Added event identity: each trigger dispatch generates a shared UUID and timestamp sent as <code>X-Event-Id<\/code> \/ <code>X-Event-Timestamp<\/code> headers and embedded in the payload under <code>event.{id,timestamp,version}<\/code><\/li>\n<li>Added smart retry routing: 5xx and 429 responses trigger an automatic retry with exponential backoff; 4xx and 3xx responses are immediately marked as permanently failed<\/li>\n<li>Added <code>permanently_failed<\/code> status for non-retryable delivery failures<\/li>\n<li>Added attempt history: each delivery attempt is recorded as a JSON array on the log entry, visible in the admin timeline view<\/li>\n<li>Added per-log retry and bulk retry REST endpoints (<code>POST \/logs\/{id}\/retry<\/code>, <code>POST \/logs\/bulk-retry<\/code>)<\/li>\n<li>Added <code>event_uuid<\/code> and <code>target_url<\/code> filter parameters to logs and queue REST endpoints<\/li>\n<li>Added date range filtering (<code>date_from<\/code>, <code>date_to<\/code>) to logs and queue list views with a shadcn-style calendar date\/time picker<\/li>\n<li>Added health observability metrics: average attempts per event, oldest pending age, queue stuck detection, WP-Cron-only warning<\/li>\n<li>Added <code>queue.log_id<\/code> column linking queue jobs to their log entries<\/li>\n<li>Updated admin UI: permanently failed badge, attempt timeline, per-row retry button, bulk retry, observability warning banners, new filter inputs<\/li>\n<li>Updated footer with a review prompt linking to WordPress.org<\/li>\n<\/ul>\n\n<h4>1.0.1 \u2014 2026-02-18<\/h4>\n\n<ul>\n<li>Fixed preview freezing when mapping fields from objects with numeric string keys (e.g. WooCommerce line_items)<\/li>\n<li>Fixed orphaned pending log entries caused by logPending() silently failing \u2014 queue jobs now carry mapping metadata and recover a proper log entry if the original ID was lost<\/li>\n<li>Enhanced normalizeValue to handle Closure, DateTimeInterface, and Traversable types<\/li>\n<li>Removed unnecessary WooCommerce hook patterns from trigger exclusions<\/li>\n<li>Improved log details display with word break for long trigger names and dates<\/li>\n<\/ul>\n\n<h4>1.0.0 \u2014 2026-02-16<\/h4>\n\n<ul>\n<li>Initial release<\/li>\n<li>Webhook dispatching from WordPress actions<\/li>\n<li>Background processing with retry mechanism<\/li>\n<li>Configurable webhook payloads<\/li>\n<li>Logging of webhook deliveries<\/li>\n<\/ul>","raw_excerpt":"Operate WordPress like modern infrastructure \u2014 turn any WordPress action into a real API event your CRMs, n8n flows, AI agents, and internal services  &hellip;","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/es-cr.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/280953","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/es-cr.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/es-cr.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/es-cr.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=280953"}],"author":[{"embeddable":true,"href":"https:\/\/es-cr.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/mateuszflowsystems"}],"wp:attachment":[{"href":"https:\/\/es-cr.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=280953"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/es-cr.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=280953"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/es-cr.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=280953"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/es-cr.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=280953"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/es-cr.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=280953"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/es-cr.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=280953"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}