{"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-09-23T11:22:03","modified_gmt":"2026-09-23T11:22:03","slug":"flowsystems-webhook-actions","status":"publish","type":"plugin","link":"https:\/\/pl.wordpress.org\/plugins\/flowsystems-webhook-actions\/","author":23439561,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"3.3.0","stable_tag":"3.3.0","tested":"7.1.2","requires":"6.0","requires_php":"8.0","requires_plugins":null,"header_name":"Webhook Actions - build automations and integrations with AI help","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":"34403f","last_updated":"2026-09-23 11:22:03","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":3063,"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","revision":3462892},"1.0.1":{"tag":"1.0.1","author":"mateuszflowsystems","date":"2026-02-18 22:35:51","revision":3464668},"1.1.0":{"tag":"1.1.0","author":"mateuszflowsystems","date":"2026-02-28 21:08:40","revision":3471793},"1.1.1":{"tag":"1.1.1","author":"mateuszflowsystems","date":"2026-03-01 12:48:16","revision":3472061},"1.10.0":{"tag":"1.10.0","author":"mateuszflowsystems","date":"2026-05-03 21:47:57","revision":3521791},"1.11.0":{"tag":"1.11.0","author":"mateuszflowsystems","date":"2026-05-06 16:15:26","revision":3524614},"1.12.0":{"tag":"1.12.0","author":"mateuszflowsystems","date":"2026-05-12 20:51:43","revision":3530355},"1.12.1":{"tag":"1.12.1","author":"mateuszflowsystems","date":"2026-05-12 21:07:31","revision":3530368},"1.12.2":{"tag":"1.12.2","author":"mateuszflowsystems","date":"2026-05-14 14:50:23","revision":3532159},"1.13.0":{"tag":"1.13.0","author":"mateuszflowsystems","date":"2026-05-18 16:12:11","revision":3535989},"1.13.1":{"tag":"1.13.1","author":"mateuszflowsystems","date":"2026-05-21 13:58:15","revision":3542097},"1.14.0":{"tag":"1.14.0","author":"mateuszflowsystems","date":"2026-06-03 21:50:04","revision":3560050},"1.14.1":{"tag":"1.14.1","author":"mateuszflowsystems","date":"2026-06-05 21:31:16","revision":3562754},"1.15.0":{"tag":"1.15.0","author":"mateuszflowsystems","date":"2026-06-07 21:59:00","revision":3563955},"1.16.0":{"tag":"1.16.0","author":"mateuszflowsystems","date":"2026-06-22 21:24:22","revision":3582387},"1.2.0":{"tag":"1.2.0","author":"mateuszflowsystems","date":"2026-03-07 20:42:42","revision":3477166},"1.2.1":{"tag":"1.2.1","author":"mateuszflowsystems","date":"2026-03-07 21:21:55","revision":3477179},"1.3.0":{"tag":"1.3.0","author":"mateuszflowsystems","date":"2026-03-15 21:22:43","revision":3483308},"1.3.1":{"tag":"1.3.1","author":"mateuszflowsystems","date":"2026-03-15 21:56:44","revision":3483321},"1.3.2":{"tag":"1.3.2","author":"mateuszflowsystems","date":"2026-03-15 22:36:07","revision":3483326},"1.4.0":{"tag":"1.4.0","author":"mateuszflowsystems","date":"2026-03-22 21:17:47","revision":3488429},"1.5.0":{"tag":"1.5.0","author":"mateuszflowsystems","date":"2026-03-23 16:43:47","revision":3489231},"1.6.0":{"tag":"1.6.0","author":"mateuszflowsystems","date":"2026-03-28 22:56:32","revision":3493588},"1.6.1":{"tag":"1.6.1","author":"mateuszflowsystems","date":"2026-03-28 23:00:04","revision":3493590},"1.6.2":{"tag":"1.6.2","author":"mateuszflowsystems","date":"2026-04-05 13:22:48","revision":3499150},"1.7.0":{"tag":"1.7.0","author":"mateuszflowsystems","date":"2026-04-27 17:44:53","revision":3516712},"1.8.0":{"tag":"1.8.0","author":"mateuszflowsystems","date":"2026-04-27 22:49:00","revision":3516863},"1.9.0":{"tag":"1.9.0","author":"mateuszflowsystems","date":"2026-05-03 15:18:40","revision":3521550},"2.0.0":{"tag":"2.0.0","author":"mateuszflowsystems","date":"2026-07-06 13:01:36","revision":3597809},"2.1.0":{"tag":"2.1.0","author":"mateuszflowsystems","date":"2026-07-06 22:48:10","revision":3598377},"2.1.1":{"tag":"2.1.1","author":"mateuszflowsystems","date":"2026-07-07 12:02:41","revision":3599003},"2.1.2":{"tag":"2.1.2","author":"mateuszflowsystems","date":"2026-07-08 15:28:44","revision":3600496},"2.1.3":{"tag":"2.1.3","author":"mateuszflowsystems","date":"2026-07-09 18:00:46","revision":3601852},"2.2.0":{"tag":"2.2.0","author":"mateuszflowsystems","date":"2026-07-14 18:29:50","revision":3607879},"2.2.1":{"tag":"2.2.1","author":"mateuszflowsystems","date":"2026-07-17 21:59:18","revision":3612024},"2.2.2":{"tag":"2.2.2","author":"mateuszflowsystems","date":"2026-07-18 15:30:18","revision":3612754},"2.3.0":{"tag":"2.3.0","author":"mateuszflowsystems","date":"2026-07-21 22:14:02","revision":3617757},"2.4.0":{"tag":"2.4.0","author":"mateuszflowsystems","date":"2026-07-26 20:51:32","revision":3623807},"2.5.0":{"tag":"2.5.0","author":"mateuszflowsystems","date":"2026-07-31 16:10:47","revision":3630218},"2.6.0":{"tag":"2.6.0","author":"mateuszflowsystems","date":"2026-08-03 11:31:40","revision":3632799},"2.7.0":{"tag":"2.7.0","author":"mateuszflowsystems","date":"2026-08-04 15:25:26","revision":3634342},"2.7.1":{"tag":"2.7.1","author":"mateuszflowsystems","date":"2026-08-12 11:56:47","revision":3643323},"2.7.2":{"tag":"2.7.2","author":"mateuszflowsystems","date":"2026-08-19 21:30:29","revision":3655361},"2.7.3":{"tag":"2.7.3","author":"mateuszflowsystems","date":"2026-08-19 22:21:18","revision":3655419},"2.7.4":{"tag":"2.7.4","author":"mateuszflowsystems","date":"2026-08-20 14:38:10","revision":3657115},"2.8.0":{"tag":"2.8.0","author":"mateuszflowsystems","date":"2026-08-23 22:44:43","revision":3662399},"2.8.1":{"tag":"2.8.1","author":"mateuszflowsystems","date":"2026-08-23 23:01:54","revision":3662410},"2.9.0":{"tag":"2.9.0","author":"mateuszflowsystems","date":"2026-08-30 15:10:38","revision":3672740},"3.0.0":{"tag":"3.0.0","author":"mateuszflowsystems","date":"2026-09-03 21:25:14","revision":3680419},"3.0.1":{"tag":"3.0.1","author":"mateuszflowsystems","date":"2026-09-04 10:59:36","revision":3681073},"3.1.0":{"tag":"3.1.0","author":"mateuszflowsystems","date":"2026-09-08 12:16:26","revision":3686572},"3.1.1":{"tag":"3.1.1","author":"mateuszflowsystems","date":"2026-09-10 14:16:24","revision":3690023},"3.2.0":{"tag":"3.2.0","author":"mateuszflowsystems","date":"2026-09-12 18:36:23","revision":3693013},"3.3.0":{"tag":"3.3.0","author":"mateuszflowsystems","date":"2026-09-23 11:22:03","revision":3709174}},"upgrade_notice":[],"ratings":{"1":0,"2":0,"3":0,"4":0,"5":2},"assets_icons":{"icon-128x128.gif":{"filename":"icon-128x128.gif","revision":3601851,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.gif":{"filename":"icon-256x256.gif","revision":3601851,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3600494,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3600494,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{"blueprint.json":{"filename":"blueprint.json","revision":3709177,"resolution":false,"location":"assets","locale":"","contents":"{\"landingPage\":\"\\\/wp-admin\\\/admin.php?page=fswa-webhook-actions#\\\/ai-builder\",\"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\":\"installPlugin\",\"pluginData\":{\"resource\":\"wordpress.org\\\/plugins\",\"slug\":\"contact-form-7\"}},{\"step\":\"activatePlugin\",\"pluginPath\":\"contact-form-7\\\/wp-contact-form-7.php\"},{\"step\":\"login\",\"username\":\"admin\",\"password\":\"password\"},{\"step\":\"runPHP\",\"code\":\"<?php\\n\\\/**\\n * Seed: a SYNCHRONOUS webhook on the `profile_update` trigger, delivering to\\n * httpbin, with a field mapping that strips credentials from the payload.\\n *\\n * Runs once (guarded by empty(getAll())). The profile_update payload is the RAW\\n * hook args, so the password \\\/ activation key live at:\\n *   args.1.data.* = old WP_User object   (3rd-arg userdata is args.2.*)\\n *   args.2.*      = new userdata array\\n * We intentionally do NOT seed example_payload \\u2014 the first real dispatch\\n * auto-captures the true payload shape for the admin field picker, and\\n * SchemaRepository::upsert() preserves this field_mapping through that capture.\\n *\\\/\\nrequire_once '\\\/wordpress\\\/wp-load.php';\\n\\nif ( ! class_exists( '\\\\FlowSystems\\\\WebhookActions\\\\Repositories\\\\WebhookRepository' ) ) {\\n    return;\\n}\\n\\n$repo = new \\\\FlowSystems\\\\WebhookActions\\\\Repositories\\\\WebhookRepository();\\n\\n\\\/\\\/ Idempotent: only seed on a fresh install.\\nif ( ! empty( $repo->getAll() ) ) {\\n    return;\\n}\\n\\n$webhookId = $repo->create([\\n    'name'           => 'Demo \\u2014 Profile Update \\u2192 httpbin',\\n    'endpoint_url'   => 'https:\\\/\\\/httpbin.org\\\/post',\\n    'http_method'    => 'POST',\\n    'is_synchronous' => 1,\\n    'is_enabled'     => 1,\\n    'triggers'       => ['profile_update'],\\n]);\\n\\nif ( $webhookId && class_exists( '\\\\FlowSystems\\\\WebhookActions\\\\Repositories\\\\SchemaRepository' ) ) {\\n    $schemaRepo = new \\\\FlowSystems\\\\WebhookActions\\\\Repositories\\\\SchemaRepository();\\n    $schemaRepo->upsert( (int) $webhookId, 'profile_update', [\\n        'field_mapping' => [\\n            'mappings'        => [],\\n            'excluded'        => [\\n                'args.1.data.user_pass',\\n                'args.1.data.user_activation_key',\\n                'args.2.user_pass',\\n                'args.2.user_activation_key',\\n            ],\\n            'includeUnmapped' => true,\\n        ],\\n    ] );\\n}\"},{\"step\":\"runPHP\",\"code\":\"<?php\\n\\\/**\\n * Seed: a \\\"subscriber sync pipeline\\\" showcasing CONDITIONS + CHAINS together,\\n * with no third-party plugin behind it.\\n *\\n *   [profile_update]\\n *        \\u2502  condition: role == subscriber   (only subscribers pass)\\n *        \\u25bc\\n *   Webhook C  \\\"Subscriber Updated \\u2192 CRM\\\"  \\u2500\\u2500chain\\u2500\\u2500\\u25b6  Webhook D  \\\"Subscriber Sync \\u2192 Audit\\\"\\n *   (synchronous, POST httpbin)                        (synchronous, POST httpbin)\\n *\\n * This replaces the old WooCommerce order-fulfilment demo (seeds 30\\\/40). Same two\\n * features, none of the cost: WooCommerce added tens of megabytes to every preview\\n * boot and queued a setup-wizard redirect that hijacked the landing screen. The\\n * `profile_update` hook is core, so this fires with zero extra dependencies.\\n *\\n * The users are created\\\/updated in a LATER step (20-fire-sample-delivery.php),\\n * which must be a separate runPHP request so these webhook + trigger rows already\\n * exist when the hook fires \\u2014 listeners attach on `init` from stored triggers.\\n *\\n * Idempotent: guarded by the chain name so long-lived previews don't duplicate.\\n *\\\/\\nrequire_once '\\\/wordpress\\\/wp-load.php';\\n\\nuse FlowSystems\\\\WebhookActions\\\\Repositories\\\\WebhookRepository;\\nuse FlowSystems\\\\WebhookActions\\\\Repositories\\\\SchemaRepository;\\nuse FlowSystems\\\\WebhookActions\\\\Repositories\\\\ChainRepository;\\nuse FlowSystems\\\\WebhookActions\\\\Repositories\\\\ChainLinkRepository;\\n\\nif ( ! class_exists( ChainRepository::class ) || ! class_exists( WebhookRepository::class ) ) {\\n    return;\\n}\\n\\n$chainRepo = new ChainRepository();\\n\\n\\\/\\\/ Idempotent: guard on this seed's own chain name. (An empty-getAll() guard would\\n\\\/\\\/ be wrong here \\u2014 seed 10 has already created a webhook by this point.)\\nif ( $chainRepo->findByName( 'Subscriber Sync Pipeline' ) ) {\\n    return;\\n}\\n\\n$webhookRepo = new WebhookRepository();\\n$schemaRepo  = new SchemaRepository();\\n$linkRepo    = new ChainLinkRepository();\\n\\n\\\/\\\/ --- Webhook C: subscriber updated \\u2192 CRM (conditional, mapped) --------------\\n$sourceId = $webhookRepo->create([\\n    'name'           => 'Subscriber Updated \\u2192 CRM',\\n    'description'    => 'Fires on profile_update, but only for subscribers. Non-subscriber updates are logged as skipped, with the failing rule shown.',\\n    'endpoint_url'   => 'https:\\\/\\\/httpbin.org\\\/post',\\n    'http_method'    => 'POST',\\n    'is_synchronous' => 1,\\n    'is_enabled'     => 1,\\n    'triggers'       => ['profile_update'],\\n]);\\n\\nif ( ! $sourceId ) {\\n    return;\\n}\\n\\n\\\/\\\/ profile_update passes ($user_id, $old_WP_User, $new_userdata), so the payload is\\n\\\/\\\/ the RAW hook args: args.0 = id, args.1.data.* = the OLD user row, args.2.* = the\\n\\\/\\\/ new userdata array. Conditions run on `original` (pre-mapping) so the rule reads\\n\\\/\\\/ the raw path and fast-fails before a pending log is written.\\n\\\/\\\/ example_payload is deliberately left unset: the first real dispatch auto-captures\\n\\\/\\\/ the true shape, and upsert() preserves this mapping through that capture.\\n$schemaRepo->upsert( (int) $sourceId, 'profile_update', [\\n    'conditions' => [\\n        'enabled' => true,\\n        'type'    => 'and',\\n        'rules'   => [\\n            [\\n                'field'    => 'args.2.role',\\n                'operator' => 'equals',\\n                'value'    => 'subscriber',\\n                'cast'     => 'string',\\n            ],\\n        ],\\n    ],\\n    'conditions_evaluate_on' => 'original',\\n    'field_mapping' => [\\n        'mappings' => [\\n            [ 'source' => 'args.0',              'target' => 'user_id' ],\\n            [ 'source' => 'args.2.role',         'target' => 'role' ],\\n            [ 'source' => 'args.2.first_name',   'target' => 'first_name' ],\\n            [ 'source' => 'args.2.last_name',    'target' => 'last_name' ],\\n            [ 'source' => 'args.1.data.user_email', 'target' => 'email' ],\\n        ],\\n        'excluded'        => [],\\n        'includeUnmapped' => false,\\n    ],\\n] );\\n\\n\\\/\\\/ --- Webhook D: chain target \\u2192 audit trail ----------------------------------\\n$targetId = $webhookRepo->create([\\n    'name'           => 'Subscriber Sync \\u2192 Audit',\\n    'description'    => 'Chain target. Runs only after the CRM webhook returns 2xx, and receives the sent payload plus the upstream response.',\\n    'endpoint_url'   => 'https:\\\/\\\/httpbin.org\\\/post',\\n    'http_method'    => 'POST',\\n    'is_synchronous' => 1,\\n    'is_enabled'     => 1,\\n    \\\/\\\/ No WP trigger \\u2014 fired only via the chain link (synthetic trigger added below).\\n    'triggers'       => [],\\n]);\\n\\nif ( ! $targetId ) {\\n    return;\\n}\\n\\n\\\/\\\/ --- Wire the chain: C \\u2500\\u2500\\u25b6 D ------------------------------------------------\\n$chainId = $chainRepo->create([\\n    'name'        => 'Subscriber Sync Pipeline',\\n    'description' => 'When a subscriber update reaches the CRM, record it in the audit trail with the CRM response.',\\n]);\\n\\nif ( ! $chainId ) {\\n    return;\\n}\\n\\n\\\/\\\/ create() also inserts the synthetic `fswa_chain_link:{linkId}` trigger row for D.\\n$linkId = $linkRepo->create( (int) $chainId, (int) $sourceId, (int) $targetId );\\n\\nif ( $linkId ) {\\n    \\\/\\\/ The chain hands D the full post-dispatch context as args[0]: C's SENT (mapped)\\n    \\\/\\\/ payload, the upstream response, and chain metadata.\\n    $schemaRepo->upsert( (int) $targetId, 'fswa_chain_link:' . $linkId, [\\n        'field_mapping' => [\\n            'mappings' => [\\n                [ 'source' => 'args.0.payload.user_id',    'target' => 'user_id' ],\\n                [ 'source' => 'args.0.payload.email',      'target' => 'email' ],\\n                [ 'source' => 'args.0.payload.role',       'target' => 'role' ],\\n                [ 'source' => 'args.0.source_webhook.name','target' => 'triggered_by' ],\\n                [ 'source' => 'args.0.response.code',      'target' => 'crm_response_code' ],\\n                [ 'source' => 'args.0.chain.depth',        'target' => 'chain_depth' ],\\n            ],\\n            'excluded'        => [],\\n            'includeUnmapped' => false,\\n        ],\\n    ] );\\n}\"},{\"step\":\"runPHP\",\"code\":\"<?php\\n\\\/**\\n * Seed: Notifications (free, 3.3.0+) \\u2014 channels, site-wide rules, one per-webhook\\n * rule, one per-webhook mute, and a short Sent history so every tab has content.\\n *\\n * Channels carry PLACEHOLDER secrets (fake Slack URL, fake Twilio SIDs): nothing\\n * here can reach a real service, and failure rules never fire in the demo anyway.\\n * The one rule that DOES fire is \\\"Daily delivery summary\\\" on `success`: it is a\\n * daily digest, so seed 20's deliveries land in the Sent tab as \\\"waiting for\\n * digest\\\" rows without any outbound send. The audit webhook from seed 12 mutes\\n * that rule, which is the per-webhook mute story on its own edit screen.\\n *\\n * Runs before 20-fire-sample-delivery.php on purpose (rules must exist when the\\n * deliveries happen). Guarded twice: the classes\\\/tables exist only from 3.3.0 \\u2014\\n * a preview booted against an older published build must simply skip this \\u2014 and\\n * an option flag stops long-lived previews from seeding twice.\\n *\\\/\\nrequire_once '\\\/wordpress\\\/wp-load.php';\\n\\nuse FlowSystems\\\\WebhookActions\\\\Repositories\\\\WebhookRepository;\\nuse FlowSystems\\\\WebhookActions\\\\Repositories\\\\NotificationChannelRepository;\\nuse FlowSystems\\\\WebhookActions\\\\Repositories\\\\NotificationRuleRepository;\\nuse FlowSystems\\\\WebhookActions\\\\Repositories\\\\NotificationLogRepository;\\nuse FlowSystems\\\\WebhookActions\\\\Services\\\\Notifications\\\\NotificationSender;\\nuse FlowSystems\\\\WebhookActions\\\\Services\\\\Notifications\\\\MessageBuilder;\\n\\nif ( ! class_exists( NotificationChannelRepository::class ) || ! class_exists( NotificationRuleRepository::class ) ) {\\n    return;\\n}\\n\\nglobal $wpdb;\\n$rulesTable = $wpdb->prefix . 'fswa_notification_rules';\\nif ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $rulesTable ) ) !== $rulesTable ) {\\n    return;\\n}\\n\\nif ( get_option( 'fswa_demo_notifications_seeded' ) ) {\\n    return;\\n}\\n\\n$channels = new NotificationChannelRepository();\\n$rules    = new NotificationRuleRepository();\\n$nlog     = new NotificationLogRepository();\\n$sender   = new NotificationSender();\\n$webhooks = new WebhookRepository();\\n\\n\\\/\\\/ --- Channels ---------------------------------------------------------------\\n$mk = static function ( string $name, string $type, array $config, array $secrets ) use ( $channels, $sender ): int {\\n    $hint = '';\\n    foreach ( $secrets as $v ) {\\n        if ( $v !== '' ) {\\n            $hint = '\\u2026' . mb_substr( $v, -4 );\\n            break;\\n        }\\n    }\\n    $id = (int) $channels->create([\\n        'name'              => $name,\\n        'type'              => $type,\\n        'config'            => $config,\\n        'secret_ciphertext' => $sender->encryptSecrets( $secrets ),\\n        'hint'              => $hint,\\n        'is_enabled'        => true,\\n    ]);\\n    \\\/\\\/ Channel names are UNIQUE: create() returns false on a re-run, so fall back\\n    \\\/\\\/ to the existing row rather than seeding rules that point at channel 0.\\n    if ( $id <= 0 ) {\\n        foreach ( $channels->getAll() as $c ) {\\n            if ( ( $c['name'] ?? '' ) === $name ) {\\n                $id = (int) $c['id'];\\n                break;\\n            }\\n        }\\n    }\\n    return $id;\\n};\\n\\n$chSlack = $mk( '#ops-alerts', 'slack', [], [ 'webhook_url' => 'https:\\\/\\\/hooks.slack.com\\\/services\\\/T0000000\\\/B0000000\\\/REPLACE_ME_x9k2' ] );\\n$chEmail = $mk( 'Ops inbox', 'email', [ 'to' => 'ops@example.com, dev@example.com', 'html' => true, 'from_name' => 'Webhook Actions' ], [] );\\n$chSms   = $mk( 'On-call SMS', 'twilio_sms', [ 'from' => '+15005550006', 'to' => '+15005550009' ], [ 'account_sid' => 'ACxxxxxxxxxxxxxxxxxxxxxxxxxxxx7f3a', 'auth_token' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx91cd' ] );\\n$chPd    = $mk( 'PagerDuty \\u00b7 Integrations', 'pagerduty', [], [ 'routing_key' => 'R0xxxxxxxxxxxxxxxxxxxxxxxxxxxx2b1e' ] );\\n$chTg    = $mk( 'Alerts bot (Telegram)', 'telegram', [ 'chat_id' => '-1001234567890' ], [ 'bot_token' => '1234567890:AAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5Qw' ] );\\n\\nif ( ! $chSlack || ! $chEmail ) {\\n    return;\\n}\\n\\n$channels->recordSent( $chSlack );\\n$channels->recordSent( $chEmail );\\n\\n\\\/\\\/ --- Site-wide rules --------------------------------------------------------\\n$rule = static fn( array $d ): int => (int) $rules->create( $d );\\n\\n$rPerm  = $rule([ 'name' => 'Page on-call when a delivery gives up', 'event' => 'permanently_failed', 'channel_ids' => array_filter([ $chSlack, $chEmail, $chPd ]), 'filters' => [] ]);\\n$r5xx   = $rule([\\n    'name'        => 'Upstream errors',\\n    'event'       => 'failed_attempt',\\n    'channel_ids' => [ $chSlack ],\\n    'filters'     => [ 'http_codes' => [ '5xx', 'transport' ], 'attempts' => [ 1 ] ],\\n    'template'    => [ 'body' => \\\"{{ webhook.name }} got HTTP {{ delivery.http_code }} on attempt {{ delivery.attempt }}\\\/{{ delivery.max_attempts }}.\\\\n{{ delivery.error_message | truncate:120 }}\\\" ],\\n]);\\n$rRetry = $rule([ 'name' => 'Retry digest', 'event' => 'retry_scheduled', 'channel_ids' => [ $chEmail ], 'filters' => [], 'digest' => 'hourly' ]);\\n$rRecov = $rule([ 'name' => 'Back to normal', 'event' => 'recovered', 'channel_ids' => [ $chSlack ], 'filters' => [] ]);\\n$rDaily = $rule([ 'name' => 'Daily delivery summary', 'event' => 'success', 'channel_ids' => [ $chEmail ], 'filters' => [], 'digest' => 'daily', 'throttle_seconds' => 3600 ]);\\nif ( $chTg ) {\\n    $rule([ 'name' => 'Skipped deliveries', 'event' => 'skipped', 'channel_ids' => [ $chTg ], 'filters' => [], 'is_enabled' => false ]);\\n}\\n\\n\\\/\\\/ --- Per-webhook rule + mute on the seed-12 webhooks -------------------------\\n$byName = [];\\nforeach ( $webhooks->getAll() as $w ) {\\n    $byName[ (string) ( $w['name'] ?? '' ) ] = (int) $w['id'];\\n}\\n$crmId   = $byName['Subscriber Updated \\u2192 CRM'] ?? null;\\n$auditId = $byName['Subscriber Sync \\u2192 Audit'] ?? null;\\n$demoId  = $byName['Demo \\u2014 Profile Update \\u2192 httpbin'] ?? null;\\n\\nif ( $crmId && $chSms ) {\\n    $rule([\\n        'name'        => 'Lost subscriber sync \\u2192 SMS',\\n        'event'       => 'permanently_failed',\\n        'webhook_id'  => $crmId,\\n        'channel_ids' => [ $chSms ],\\n        'filters'     => [ 'reason' => 'exhausted' ],\\n        'template'    => [ 'short' => '{{ args.2.user_email | default:\\\"A subscriber\\\" }} did not reach the CRM after {{ delivery.attempt }} tries \\u2014 {{ delivery.log_url }}' ],\\n    ]);\\n}\\nif ( $auditId && $rDaily ) {\\n    $webhooks->update( $auditId, [ 'notifications_mode' => 'inherit', 'muted_rule_ids' => [ $rDaily ] ] );\\n}\\n\\n\\\/\\\/ --- Sent history: a believable last two days -------------------------------\\n$site = get_bloginfo( 'name' );\\n$row  = static function ( int $minsAgo, ?int $w, int $ruleId, ?int $chId, string $event, string $status, string $subject, ?string $error = null ) use ( $nlog, $wpdb, $site, $webhooks ): void {\\n    if ( ! $ruleId ) {\\n        return;\\n    }\\n    $name    = 'Webhook';\\n    if ( $w ) {\\n        $wh   = $webhooks->find( $w );\\n        $name = (string) ( $wh['name'] ?? $name );\\n    }\\n    $subject = '[' . $site . '] ' . str_replace( '{w}', $name, $subject );\\n    $id      = $nlog->create([\\n        'rule_id'    => $ruleId,\\n        'channel_id' => $chId,\\n        'webhook_id' => $w,\\n        'log_id'     => null,\\n        'event'      => $event,\\n        'status'     => $status,\\n        'subject'    => $subject,\\n        'error'      => $error,\\n        'message'    => [ 'subject' => $subject, 'title' => $subject, 'body' => '', 'short' => $subject, 'event' => $event, 'severity' => MessageBuilder::severity( $event ), 'fields' => [], 'webhook_name' => $name, 'site_name' => $site ],\\n    ]);\\n    if ( ! $id ) {\\n        return;\\n    }\\n    $ts = gmdate( 'Y-m-d H:i:s', time() - $minsAgo * 60 );\\n    $wpdb->update(\\n        $wpdb->prefix . 'fswa_notification_log',\\n        [ 'created_at' => $ts, 'sent_at' => $status === 'sent' ? $ts : null, 'attempts' => $status === 'failed' ? 3 : ( $status === 'sent' ? 1 : 0 ) ],\\n        [ 'id' => $id ]\\n    );\\n};\\n\\n$row( 12,   $crmId,  $r5xx,   $chSlack, 'failed_attempt',     'sent',      '{w} failed (attempt 1\\\/5)' );\\n$row( 11,   $crmId,  $rRetry, $chEmail, 'retry_scheduled',    'digested',  '{w} will retry (attempt 1 failed)' );\\n$row( 9,    $crmId,  $rRecov, $chSlack, 'recovered',          'sent',      '{w} recovered' );\\n$row( 75,   $demoId, $rPerm,  $chSlack, 'permanently_failed', 'sent',      '{w} permanently failed' );\\n$row( 75,   $demoId, $rPerm,  $chEmail, 'permanently_failed', 'sent',      '{w} permanently failed' );\\n$row( 75,   $demoId, $rPerm,  $chPd,    'permanently_failed', 'failed',    '{w} permanently failed', 'HTTP 400: {\\\"status\\\":\\\"invalid event\\\",\\\"message\\\":\\\"Event object is invalid\\\",\\\"errors\\\":[\\\"\\\\'routing_key\\\\' is missing or blank\\\"]}' );\\n$row( 1500, $auditId, $rRetry, $chEmail, 'retry_scheduled',   'in_digest', '{w} will retry (attempt 2 failed)' );\\n$row( 1500, $auditId, $rRetry, $chEmail, 'retry_scheduled',   'in_digest', '{w} will retry (attempt 1 failed)' );\\n$row( 1440, null,    $rRetry, $chEmail, 'retry_scheduled',    'sent',      '2 notifications \\u2014 Retry digest' );\\n\\nupdate_option( 'fswa_demo_notifications_seeded', 1, false );\"},{\"step\":\"runPHP\",\"code\":\"<?php\\n\\\/**\\n * Seed: create two example users and update their profiles, firing the\\n * `profile_update` trigger twice so the Logs view is populated on landing.\\n *\\n * Two users, deliberately:\\n *   jane.customer  role=subscriber  \\u2192 passes seed 12's condition\\n *                                     \\u2192 Webhook A success, Webhook C success,\\n *                                       and the C \\u2500\\u2500\\u25b6 D chain fires\\n *   sam.visitor    role=contributor \\u2192 fails seed 12's condition\\n *                                     \\u2192 Webhook A success, Webhook C SKIPPED\\n *                                       (with the failing rule shown)\\n *\\n * So a visitor lands on a Logs screen showing a successful delivery, a chained\\n * delivery, and a skipped one with its reason \\u2014 the whole conditions + chains\\n * story without WooCommerce.\\n *\\n * This MUST be a separate runPHP step (= separate request) from the webhook seeds\\n * above: trigger listeners are attached on `init` from the stored triggers, so the\\n * webhook + trigger rows must already exist in the DB before this request runs\\n * wp_update_user(). wp_insert_user() does NOT fire profile_update; wp_update_user()\\n * on an existing user does.\\n *\\n * `role` is passed explicitly on the update because seed 12's condition reads\\n * args.2.role \\u2014 the NEW userdata array. Omit it and the key is absent, the rule\\n * cannot match, and both users would be skipped.\\n *\\\/\\nrequire_once '\\\/wordpress\\\/wp-load.php';\\n\\n$demoUsers = [\\n    [\\n        'login'       => 'jane.customer',\\n        'email'       => 'jane.customer@example.com',\\n        'first'       => 'Jane',\\n        'last'        => 'Customer',\\n        'role'        => 'subscriber',\\n        'description' => 'Example subscriber. Updating this profile fires a webhook that PASSES the \\\"subscribers only\\\" condition and chains onward to the audit webhook.',\\n    ],\\n    [\\n        'login'       => 'sam.visitor',\\n        'email'       => 'sam.visitor@example.com',\\n        'first'       => 'Sam',\\n        'last'        => 'Visitor',\\n        'role'        => 'contributor',\\n        'description' => 'Example contributor. Updating this profile fires the same webhook, which is SKIPPED because the \\\"subscribers only\\\" condition fails.',\\n    ],\\n];\\n\\nforeach ( $demoUsers as $demo ) {\\n    $uid = username_exists( $demo['login'] );\\n\\n    if ( ! $uid ) {\\n        $uid = wp_insert_user([\\n            'user_login'  => $demo['login'],\\n            'user_pass'   => wp_generate_password(),\\n            'user_email'  => $demo['email'],\\n            'first_name'  => $demo['first'],\\n            'last_name'   => $demo['last'],\\n            'role'        => $demo['role'],\\n            'description' => 'Example account for the webhook demo.',\\n        ]);\\n    }\\n\\n    if ( ! $uid || is_wp_error( $uid ) ) {\\n        continue;\\n    }\\n\\n    \\\/\\\/ The update is what fires profile_update. `role` must be present in this\\n    \\\/\\\/ array \\u2014 seed 12's condition matches on args.2.role.\\n    wp_update_user([\\n        'ID'          => $uid,\\n        'first_name'  => $demo['first'],\\n        'last_name'   => $demo['last'] . ' (updated)',\\n        'role'        => $demo['role'],\\n        'description' => $demo['description'],\\n    ]);\\n}\"}]}"}},"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.12.2","1.13.0","1.13.1","1.14.0","1.14.1","1.15.0","1.16.0","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","2.0.0","2.1.0","2.1.1","2.1.2","2.1.3","2.2.0","2.2.1","2.2.2","2.3.0","2.4.0","2.5.0","2.6.0","2.7.0","2.7.1","2.7.2","2.7.3","2.7.4","2.8.0","2.8.1","2.9.0","3.0.0","3.0.1","3.1.0","3.1.1","3.2.0","3.3.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3597783,"resolution":"1","location":"assets","locale":"","width":1641,"height":880},"screenshot-10.png":{"filename":"screenshot-10.png","revision":3597783,"resolution":"10","location":"assets","locale":"","width":794,"height":785},"screenshot-11.png":{"filename":"screenshot-11.png","revision":3597783,"resolution":"11","location":"assets","locale":"","width":822,"height":925},"screenshot-12.png":{"filename":"screenshot-12.png","revision":3597783,"resolution":"12","location":"assets","locale":"","width":809,"height":878},"screenshot-13.png":{"filename":"screenshot-13.png","revision":3597783,"resolution":"13","location":"assets","locale":"","width":1638,"height":865},"screenshot-14.png":{"filename":"screenshot-14.png","revision":3709172,"resolution":"14","location":"assets","locale":"","width":1648,"height":888},"screenshot-15.png":{"filename":"screenshot-15.png","revision":3709172,"resolution":"15","location":"assets","locale":"","width":1648,"height":888},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3597783,"resolution":"2","location":"assets","locale":"","width":1641,"height":798},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3597783,"resolution":"3","location":"assets","locale":"","width":841,"height":902},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3597783,"resolution":"4","location":"assets","locale":"","width":824,"height":900},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3597783,"resolution":"5","location":"assets","locale":"","width":822,"height":888},"screenshot-6.png":{"filename":"screenshot-6.png","revision":3597783,"resolution":"6","location":"assets","locale":"","width":713,"height":874},"screenshot-7.png":{"filename":"screenshot-7.png","revision":3597783,"resolution":"7","location":"assets","locale":"","width":1635,"height":840},"screenshot-8.png":{"filename":"screenshot-8.png","revision":3597783,"resolution":"8","location":"assets","locale":"","width":1635,"height":840},"screenshot-9.png":{"filename":"screenshot-9.png","revision":3597783,"resolution":"9","location":"assets","locale":"","width":1650,"height":765}},"screenshots":{"1":"Build with AI \u2014 describe the integration in chat; the agent plans, builds, and tests it step by step, with progress in the sidebar and one-click enable when the build completes","2":"Webhooks list view","3":"Webhook configuration screen","4":"Selecting WordPress action triggers","5":"Payload mapping configuration","6":"Webhook delivery logs with replay and retry controls","7":"Queue status overview","8":"Settings configuration screen","9":"REST API Tokens configuration screen","10":"Conditional webhook dispatch \u2014 conditions editor","11":"Test webhook drawer \u2014 send a test delivery and inspect request details inline","12":"Webhook Chains \u2014 pick an existing chain or create a new one, then select which upstream webhooks should fire this one on their 2xx response","13":"Credentials Vault \u2014 store reusable authentication secrets (Bearer, Basic, API key, custom) encrypted at rest and reference them from webhooks instead of pasting raw Authorization headers","14":"Notifications \u2014 site-wide rules that decide when a message goes out (a failed attempt, a scheduled retry, a delivery that gave up, a recovery, a daily summary) and to which channels, with quiet times and digests","15":"Notification rule editor \u2014 subject, title, body and SMS one-liner templates with <code>{{ }}<\/code> placeholders, a live preview rendered against the captured payload with the standard facts, and the quiet-time and digest controls"}},"plugin_section":[],"plugin_tags":[2353,569,243637,34953,141701],"plugin_category":[45],"plugin_contributors":[255989],"plugin_business_model":[],"class_list":["post-280953","plugin","type-plugin","status-publish","hentry","plugin_tags-ai","plugin_tags-automation","plugin_tags-n8n","plugin_tags-webhooks","plugin_tags-zapier","plugin_category-ecommerce","plugin_contributors-mateuszflowsystems","plugin_committers-mateuszflowsystems"],"banners":{"banner":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/banner-772x250.png?rev=3600494","banner_2x":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/banner-1544x500.png?rev=3600494","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/icon-128x128.gif?rev=3601851","icon_2x":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/icon-256x256.gif?rev=3601851","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-1.png?rev=3597783","caption":"Build with AI \u2014 describe the integration in chat; the agent plans, builds, and tests it step by step, with progress in the sidebar and one-click enable when the build completes"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-2.png?rev=3597783","caption":"Webhooks list view"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-3.png?rev=3597783","caption":"Webhook configuration screen"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-4.png?rev=3597783","caption":"Selecting WordPress action triggers"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-5.png?rev=3597783","caption":"Payload mapping configuration"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-6.png?rev=3597783","caption":"Webhook delivery logs with replay and retry controls"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-7.png?rev=3597783","caption":"Queue status overview"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-8.png?rev=3597783","caption":"Settings configuration screen"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-9.png?rev=3597783","caption":"REST API Tokens configuration screen"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-10.png?rev=3597783","caption":"Conditional webhook dispatch \u2014 conditions editor"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-11.png?rev=3597783","caption":"Test webhook drawer \u2014 send a test delivery and inspect request details inline"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-12.png?rev=3597783","caption":"Webhook Chains \u2014 pick an existing chain or create a new one, then select which upstream webhooks should fire this one on their 2xx response"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-13.png?rev=3597783","caption":"Credentials Vault \u2014 store reusable authentication secrets (Bearer, Basic, API key, custom) encrypted at rest and reference them from webhooks instead of pasting raw Authorization headers"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-14.png?rev=3709172","caption":"Notifications \u2014 site-wide rules that decide when a message goes out (a failed attempt, a scheduled retry, a delivery that gave up, a recovery, a daily summary) and to which channels, with quiet times and digests"},{"src":"https:\/\/ps.w.org\/flowsystems-webhook-actions\/assets\/screenshot-15.png?rev=3709172","caption":"Notification rule editor \u2014 subject, title, body and SMS one-liner templates with <code>{{ }}<\/code> placeholders, a live preview rendered against the captured payload with the standard facts, and the quiet-time and digest controls"}],"raw_content":"<!--section=description-->\n<p><strong>Describe the integration you want. The AI builds it.<\/strong> Webhook Actions ships with <strong>Build with AI<\/strong> \u2014 an in-admin agent that turns a plain-language request like <em>\"When a Contact Form 7 form is submitted, send it as JSON to my n8n webhook\"<\/em> into a working, tested automation. The agent proposes a plan you can review and edit, then creates the webhook, works from a real example payload \u2014 your site's own capture, or the Payload Library for an event your site has never fired \u2014 maps the fields, sets dispatch conditions, probes your endpoint, and sends a test delivery. Nothing goes live without your confirmation \u2014 new webhooks are always created disabled, and you can undo the last change with one click.<\/p>\n\n<p><strong>It reads the API before it builds.<\/strong> Name HubSpot, OpenAI, Slack, Stripe, Notion or any of the 300+ services in the <a href=\"https:\/\/wpwebhooks.org\/api-library\/\">WP Webhooks API Docs Library<\/a> and the agent works from that vendor's <em>current<\/em> API reference \u2014 endpoint, auth header, body envelope, the errors people actually hit \u2014 not from the model's memory of it. A service the library has never seen is researched from the vendor's own docs on the spot, and every reply built this way says so.<\/p>\n\n<p>\ud83d\udcd6 <a href=\"https:\/\/wpwebhooks.org\/docs\/\">Full documentation at wpwebhooks.org\/docs\/<\/a>\n\u25b6\ufe0f <a href=\"https:\/\/playground.wordpress.net\/?blueprint-url=https:\/\/wpwebhooks.org\/blueprint.json\">Try it in your browser \u2014 no install, no signup, no API key<\/a><\/p>\n\n<h4>What you can connect<\/h4>\n\n<p><strong>Sources \u2014 anything in WordPress that fires an action.<\/strong> Webhook Actions turns any <code>do_action<\/code> into a trigger, so form submissions, WooCommerce orders, user registrations, post publishes and your own custom plugin events can all start an automation. That covers Elementor Forms, WPForms, Forminator, Fluent Forms, Gravity Forms, WooCommerce and your own code \u2014 there is no per-plugin add-on to hunt down. Contact Form 7 and IvyForms go one step further with built-in support: their submissions are normalized into clean JSON payloads automatically.<\/p>\n\n<p><strong>Destinations \u2014 any HTTP endpoint.<\/strong> The plugin sends outgoing webhooks to anything that accepts a request: an n8n, Make (formerly Integromat), Zapier or Pabbly webhook node, a Slack or Discord incoming-webhook URL for order and form notifications, Airtable, Google Sheets, Mailchimp, HubSpot, Salesforce, Notion, your CRM, an internal microservice, or an AI agent API. There are no bundled per-service connectors and none are needed \u2014 point a webhook at a URL, map the fields, and send. Every delivery is queued, retried on failure and logged with full request and response history.<\/p>\n\n<p>That makes it a no-code way to sync WordPress data outward: describe what you want in chat and let the AI build it, or wire it up by hand in the admin UI. No PHP required either way.<\/p>\n\n<h4>No API key needed to start<\/h4>\n\n<ul>\n<li><strong>55 free credits, no key and no signup<\/strong> \u2014 claimed automatically on your first prompt. There is no button to press, no account to create and no card; the plugin sends nothing but your site address. That is about five agent turns, or roughly two complete automations built end to end<\/li>\n<li><strong>Try it without installing anything<\/strong> \u2014 <a href=\"https:\/\/playground.wordpress.net\/?blueprint-url=https:\/\/wpwebhooks.org\/blueprint.json\">Live Preview<\/a> (also the button at the top of this page) boots a throwaway WordPress in your browser and runs the real agent on those credits<\/li>\n<li><strong>WordPress connectors<\/strong> \u2014 if your site already has an AI provider connected (Settings \u2192 Connectors), the builder uses it directly and the plugin stores no keys<\/li>\n<li><strong>My own keys<\/strong> \u2014 connect Anthropic, OpenAI or Google in the builder; keys are encrypted in the Credentials Vault and never returned over the API. A free Google AI Studio key gives you Gemini at no cost: <a href=\"https:\/\/wpwebhooks.org\/docs\/get-google-ai-studio-api-key\/\">step-by-step tutorial<\/a><\/li>\n<li><strong>Your own key always wins<\/strong> \u2014 once a provider of yours is connected, the free credits are never spent<\/li>\n<li><strong>Automatic fallback<\/strong> \u2014 if a provider is rate-limited mid-build, the agent switches to another connected provider and keeps going<\/li>\n<\/ul>\n\n<h4>What the AI works from<\/h4>\n\n<p>The agent doesn't guess \u2014 it works from real payloads. It maps fields against actually captured payloads, edits existing webhooks by name or id instead of duplicating them, validates endpoints with a guarded probe (SSRF-protected, secrets always redacted), and verifies the result with a real test delivery. Every operation is also published as a WordPress Ability, so external AI tools (Claude Code, Cursor) can drive the same toolset over the Model Context Protocol (MCP) with scoped API tokens.<\/p>\n\n<h4>The Payload Library<\/h4>\n\n<p>Mapping fields needs an example payload, and until a trigger fires on your site there is nothing to map against \u2014 which is worst on the events that are hardest to produce on demand: a refunded order, a cancelled subscription, a deleted user.<\/p>\n\n<p>The Payload Library closes that gap with hundreds of hook payloads captured on our own test sites \u2014 WordPress core, WooCommerce, ACF and the major form plugins \u2014 so the agent can work from the real shape of an event your site has never fired. Your own capture always wins the moment the event really fires, and a reference payload is always labelled as one: the trigger panel shows a \"WP Webhooks Payload Library\" badge naming the plugin build it came off, and every build made from one ends with a test delivery before the webhook can go live.<\/p>\n\n<p>What a reference payload cannot know is your own keys. Fields inside containers your site defines \u2014 a form's fields, post or order meta, ACF \u2014 are never mapped from it; the agent pauses, names the paths, and asks you to fire the event once. Lookups run while Build with AI is on WP Webhooks AI (the free trial or Pro credits) and cost no credits.<\/p>\n\n<h4>The API Docs Library<\/h4>\n\n<p>A language model remembers an API the way it was documented when the model was trained: an old version path, a body envelope that has since changed, a required field it never saw. Build with AI does not build from that memory. When a prompt names a service the library knows \u2014 HubSpot, Airtable, Slack, Notion, Stripe, Shopify, OpenAI, Anthropic, Gemini and <a href=\"https:\/\/wpwebhooks.org\/api-library\/\">300+ more<\/a> \u2014 WP Webhooks AI reads that service's current reference card on our server and puts it in front of the model before it plans: the exact endpoint and method, how the auth token enters the plan (as a credential step, not a question in chat), the body envelope, request-level flags, and the 4xx responses that come up in practice.<\/p>\n\n<p>A service nobody has a card for yet is researched on the spot from the vendor's own documentation \u2014 the chat says \"Reading \u2026's API reference for the first time\" while it waits, usually under a minute, and the wait costs no credits \u2014 and the card is kept for everyone. Every reply built from a card carries a \"WP Webhooks API Docs Library\" pill naming the service, the operation and the date the reference was verified, linking to the vendor's page. Available while Build with AI runs on WP Webhooks AI (the free trial or Pro credits); about one credit on the turn that uses a card.<\/p>\n\n<h4>Notifications<\/h4>\n\n<p>Retries handle the flaky minute; Notifications handle the endpoint that stays down at 3 a.m. Get told when a delivery fails, retries, gives up, is skipped, succeeds or recovers \u2014 by email, Slack, Discord, Telegram, Microsoft Teams, Google Chat, Mattermost, Pushover, ntfy, SMS and WhatsApp through Twilio, PagerDuty (an incident that opens on failure and resolves on recovery), or any URL as JSON. Rules decide when and where: which attempt, which HTTP codes, which triggers; a quiet time per webhook so a bad hour is one message, not a hundred; hourly or daily digests. Every webhook inherits the site-wide rules, can mute single ones, keep only its own, or switch notifications off. Every message is a template with <code>{{ payload.order.id }}<\/code>-style placeholders, a field picker, a live preview against your captured payload, a linter \u2014 and a Draft with AI button. Channel secrets are encrypted and never shown again. Entirely free. <a href=\"https:\/\/wpwebhooks.org\/docs\/alert-on-failed-webhooks\/\">Five-minute setup guide<\/a> \u00b7 <a href=\"https:\/\/wpwebhooks.org\/features\/notifications\/\">Feature overview<\/a><\/p>\n\n<h4>The engine underneath (free)<\/h4>\n\n<ul>\n<li>Turn any WordPress do_action into a first-class automation trigger your CRMs, n8n flows, AI agents, and internal services can consume \u2014 every dispatch is an outgoing webhook you fully control<\/li>\n<li>Persistent delivery queue with smart retry and exponential backoff \u2014 powered by WP-Cron, auto-upgrades to Action Scheduler or System Cron when available, <strong>(Pro)<\/strong> External Cron for guaranteed reliability<\/li>\n<li>Per-event UUID and ISO 8601 timestamp \u2014 enable downstream deduplication<\/li>\n<li>Delivery logs with full attempt history, request\/response inspection, replay, and bulk retry<\/li>\n<li>Code Glue \u2014 attach PHP snippets to any webhook+trigger to reshape the payload before dispatch or run side effects after the response, with a preview that runs your code against a real captured payload first. Build with AI can draft, test-run and assign them for you<\/li>\n<li>Per-webhook retry limit and backoff strategy (exponential, linear or fixed), each falling back to a site-wide default<\/li>\n<li>Synchronous execution mode \u2014 fire inline without queue delay<\/li>\n<li>Payload mapping \u2014 rename, restructure, exclude, and type-cast fields with dot-notation paths<\/li>\n<li>Conditional dispatch \u2014 filter events by payload field values before dispatch, so a Slack notification or a CRM sync only fires when it should. Build as many rules as you need and group them with AND\/OR matching, by hand or by describing them to Build with AI<\/li>\n<li>HTTP method, custom headers, and URL query parameters per webhook<\/li>\n<li>Dynamic endpoint URLs \u2014 <code>{{ field.path }}<\/code> placeholders resolved against the payload at dispatch time, or the <code>fswa_webhook_url<\/code> filter if you would rather do it in PHP<\/li>\n<li>Webhook Chains \u2014 wire 2xx completions to downstream webhooks with full observability<\/li>\n<li>Import &amp; Export \u2014 move webhooks and chains between sites as portable JSON (triggers, field mapping, conditions and Code Glue snippets included), with strict validation and a per-item result summary on import<\/li>\n<li>Markdown descriptions \u2014 document what each webhook and chain does inline, with a Write\/Preview toggle while editing<\/li>\n<li>Notifications \u2014 email, Slack, Discord, Telegram, Teams, SMS, PagerDuty and more when a delivery fails, gives up or recovers; rules with filters, quiet times and digests; templated messages the AI can draft<\/li>\n<li>Credentials Vault \u2014 store reusable auth secrets (Bearer, Basic, API key, custom) encrypted at rest; reference them from webhooks instead of pasting raw Authorization headers. Secrets are write-only over the API \u2014 never returned, only a masked hint<\/li>\n<li>Activity History \u2014 persistent audit log of every admin and API-token action<\/li>\n<li>Built-in CF7 and IvyForms integrations \u2014 structured payloads, no extra plugins<\/li>\n<li>Action Scheduler auto-detection \u2014 more reliable delivery on high-traffic sites<\/li>\n<li>Fully translatable \u2014 the entire admin interface and all server-side strings are internationalized; ships with Polish, Simplified Chinese, and Dutch, and is compatible with WPML and Polylang String Translation<\/li>\n<li>Full REST API with scoped API token authentication (<code>read<\/code> \/ <code>operational<\/code> \/ <code>full<\/code> \/ <code>agent<\/code>) \u2014 the <code>agent<\/code> scope grants full write access for AI assistants while never exposing stored secrets<\/li>\n<li>Developer extensibility \u2014 26 filters and 11 action hooks (<a href=\"https:\/\/wpwebhooks.org\/docs\/\">reference<\/a>)<\/li>\n<\/ul>\n\n<h4>Pro features<\/h4>\n\n<ul>\n<li>AI credits included \u2014 Build with AI runs through the hosted WP Webhooks AI service on every Pro plan: no API keys to create, no provider accounts, a monthly credit allowance that renews automatically, and a live credits counter in the builder. Your own keys and WordPress connectors stay available any time<\/li>\n<li>External Cron \u2014 replace unreliable visitor-triggered WP-Cron with a managed external pinger, provisioned automatically on license activation. Two modes: plugin queue endpoint (down to 20 s interval, configurable batch size) or WP-Cron endpoint (60 s, covers all WordPress background work). No server crontab or external dashboard \u2014 controlled entirely from wp-admin, with a live heartbeat chart and inline error alerts<\/li>\n<\/ul>\n\n<p><a href=\"https:\/\/wpwebhooks.org\/pricing\/\">See pricing and upgrade \u2192<\/a><\/p>\n\n<h4>Examples<\/h4>\n\n<ul>\n<li><a href=\"https:\/\/wpwebhooks.org\/examples\/cf7-to-webhook\/\">Send Contact Form 7 submissions to a webhook (n8n demo)<\/a><\/li>\n<li><a href=\"https:\/\/wpwebhooks.org\/examples\/gravity-forms-webhooks\/\">Send Gravity Forms Submissions to n8n<\/a><\/li>\n<li><a href=\"https:\/\/wpwebhooks.org\/examples\/ivyforms-to-webhook\/\">Send IvyForms submissions to a webhook (n8n demo)<\/a><\/li>\n<li><a href=\"https:\/\/wpwebhooks.org\/examples\/woocommerce-order-webhook-claude-code\/\">WooCommerce orders to n8n on completion \u2014 wired up with a Claude Code agent<\/a><\/li>\n<li><a href=\"https:\/\/wpwebhooks.org\/examples\/hubspot-woocommerce-integration\/\">WooCommerce to HubSpot integration \u2014 sync orders, contacts, and deals with no custom code<\/a><\/li>\n<\/ul>\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 \u2014 it opens on <strong>Build with AI<\/strong>.<\/li>\n<li>Connect an AI provider (or use your WordPress 7.0 AI connectors) and describe the integration you want \u2014 or skip the AI and configure webhooks manually under the Webhooks tab.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"does%20the%20ai%20builder%20need%20an%20api%20key%3F%20is%20it%20free%20to%20use%3F\"><h3>Does the AI Builder need an API key? Is it free to use?<\/h3><\/dt>\n<dd><p>No key, and nothing to sign up for. Your first prompt automatically claims <strong>55 free credits<\/strong> \u2014 no account, no card, and the only thing the plugin sends is your site address. That is about five agent turns, or roughly two complete automations built end to end, which is enough to find out whether this works for you before committing anything.<\/p>\n\n<p>When the credits run out you have three ways to carry on, and two of them are free. If your site has an AI provider connected in WordPress (Settings \u2192 Connectors), the builder uses it with no extra setup. Otherwise bring your own Anthropic, OpenAI or Google key \u2014 a free Google AI Studio key gives you Gemini at no cost. <a href=\"https:\/\/wpwebhooks.org\/docs\/get-google-ai-studio-api-key\/\">Here's how to get one in two minutes \u2192<\/a> Or move to Pro, which includes a hosted credit pool and no keys at all. Whichever you pick, a site with its own key never spends the free credits.<\/p><\/dd>\n<dt id=\"is%20my%20data%20safe%20with%20the%20ai%20builder%3F\"><h3>Is my data safe with the AI Builder?<\/h3><\/dt>\n<dd><p>Yes. Your provider API keys are encrypted in the Credentials Vault and never returned over the API. Stored webhook credentials are never sent to the AI model, and captured payload values whose field names look sensitive (passwords, tokens, keys) are redacted before any prompt is built. The agent's changes run locally in the plugin \u2014 the model only proposes the plan.<\/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 two things, both of which run on our infrastructure rather than yours: included AI credits for Build with AI (hosted, no API keys needed) and External Cron (activated automatically on license activation). Everything the plugin does \u2014 Code Glue, unlimited AND\/OR conditions, per-webhook retry and backoff, dynamic URL templates, and the AI agent that can build all of them for you \u2014 is free as of 3.0.0. <a href=\"https:\/\/wpwebhooks.org\/pricing\/\">Learn more \u2192<\/a><\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20woocommerce%2C%20n8n%2C%20make%2C%20zapier%2C%20and%20ai%20agents%3F\"><h3>Does it work with WooCommerce, n8n, Make, Zapier, and AI agents?<\/h3><\/dt>\n<dd><p>Yes. Any WordPress or WooCommerce action can be a trigger. The plugin delivers to any HTTP endpoint \u2014 n8n, Make, Zapier webhook nodes, internal services, or AI agent APIs. Scoped API tokens let Claude Code, Cursor, or any automation tool read logs, retry deliveries, and toggle webhooks without WordPress credentials.<\/p><\/dd>\n<dt id=\"is%20this%20a%20zapier%20or%20make%20alternative%3F\"><h3>Is this a Zapier or Make alternative?<\/h3><\/dt>\n<dd><p>For the WordPress half of the job, yes \u2014 and it is worth being precise about which half. Zapier, Make and Pabbly are multi-app orchestrators with large connector catalogues and a visual canvas. If your workflow joins several SaaS products that have nothing to do with WordPress, that is what they are for, and this plugin is not an alternative to it.<\/p>\n\n<p>What it does replace is the metered hop out of WordPress. Rather than spending a task every time an order or a form entry leaves your site, your own server sends it \u2014 queued, retried with exponential backoff, and logged with full request and response history. Webhook Chains let one delivery trigger the next on a 2xx response, so a multi-step flow can stay on your own infrastructure. There is no connector catalogue here: you point a webhook at a URL and map the fields.<\/p>\n\n<p>Plenty of sites run both \u2014 this plugin as the unmetered, self-hosted exit from WordPress, with a Zapier or Make node on the far end when a workflow genuinely needs their catalogue.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20elementor%20forms%2C%20wpforms%2C%20forminator%2C%20fluent%20forms%20or%20gravity%20forms%3F\"><h3>Does it work with Elementor Forms, WPForms, Forminator, Fluent Forms or Gravity Forms?<\/h3><\/dt>\n<dd><p>Yes. These plugins fire their own WordPress actions when a form is submitted, and Webhook Actions can use any of them as a trigger \u2014 so you can send an Elementor Forms or WPForms submission straight to a webhook without a dedicated add-on. Pick the plugin's submit action in the trigger list, capture a real submission to see the payload, then map the fields you want. Contact Form 7 and IvyForms additionally ship with built-in normalization; the others use the generic trigger path.<\/p><\/dd>\n<dt id=\"can%20i%20send%20wordpress%20data%20to%20slack%2C%20google%20sheets%2C%20airtable%20or%20a%20crm%3F\"><h3>Can I send WordPress data to Slack, Google Sheets, Airtable or a CRM?<\/h3><\/dt>\n<dd><p>Yes \u2014 any destination that accepts an HTTP request works. For Slack or Discord, paste the incoming-webhook URL they give you and map the payload into the message field. For Google Sheets, Airtable, Mailchimp, HubSpot or Salesforce, either call their API directly or point the webhook at an n8n, Make or Zapier node and let it do the last hop. Nothing here needs a per-service connector, because the plugin speaks plain HTTP.<\/p><\/dd>\n<dt id=\"do%20i%20need%20extra%20plugins%20for%20contact%20form%207%20or%20ivyforms%3F\"><h3>Do I need extra plugins for Contact Form 7 or IvyForms?<\/h3><\/dt>\n<dd><p>No. Both integrations are built in. When CF7 or IvyForms is active, submissions are automatically normalized into clean JSON payloads \u2014 no additional plugins or custom code required.<\/p><\/dd>\n<dt id=\"can%20it%20alert%20me%20when%20a%20webhook%20fails%3F\"><h3>Can it alert me when a webhook fails?<\/h3><\/dt>\n<dd><p>Yes, for free. Notifications \u2192 Channels holds where messages go (email, Slack, Discord, Telegram, Teams, Google Chat, Mattermost, Pushover, ntfy, Twilio SMS\/WhatsApp, PagerDuty, any URL); Notifications \u2192 Rules decides when \u2014 a failed attempt, a scheduled retry, a delivery that gave up, a skipped event, a success, or a recovery \u2014 with filters on the attempt number, HTTP code and trigger, a quiet time per webhook and hourly or daily digests. Each webhook can inherit the site-wide rules, mute some, or have its own. The Sent tab shows every message and its status.<\/p><\/dd>\n<dt id=\"how%20does%20retry%20work%3F\"><h3>How does retry work?<\/h3><\/dt>\n<dd><p>The dispatcher retries 5xx and 429 responses automatically with exponential backoff. The delay before attempt N is <code>base_delay \u00d7 2^N<\/code>, capped at the maximum delay \u2014 so on the defaults (exponential, 30s base, 1 hour cap, 5 attempts) a failing delivery is retried after 60s, 120s, 240s and 480s, and is marked <code>permanently_failed<\/code> roughly 15 minutes after the first attempt. The 1 hour cap only comes into play if you raise the attempt limit or the base delay. 4xx and 3xx responses are marked <code>permanently_failed<\/code> immediately \u2014 bad payloads are not worth retrying. Override the attempt limit per webhook in the UI, or globally with the <code>fswa_max_attempts<\/code> filter; the backoff strategy (exponential, linear or fixed) and its base and maximum delays are per-webhook settings too, each falling back to a site-wide default.<\/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 a token from the API Tokens screen and pass it as <code>X-FSWA-Token: &lt;token&gt;<\/code> (or <code>Authorization: Bearer<\/code>). Four scopes available \u2014 <code>read<\/code>, <code>operational<\/code>, <code>full<\/code>, and <code>agent<\/code> (full write access for AI assistants that never exposes stored secrets) \u2014 so you can grant exactly the access each integration needs. Full API reference at <a href=\"https:\/\/wpwebhooks.org\/webhook-wordpress-plugin-api\/\">wpwebhooks.org\/webhook-wordpress-plugin-api\/<\/a><\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<p>For the full release history see <a href=\"https:\/\/wpwebhooks.org\/changelog\/\">wpwebhooks.org\/changelog\/<\/a><\/p>\n\n<h4>3.3.0 \u2014 2026-09-23<\/h4>\n\n<ul>\n<li>Added: Notifications. Get told when a delivery fails, retries, gives up, is skipped, succeeds or recovers \u2014 by email, Slack, Discord, Telegram, Microsoft Teams (Workflows), Google Chat, Mattermost\/Rocket.Chat, Pushover, ntfy, SMS and WhatsApp through Twilio, PagerDuty (an incident that opens on failure and resolves on recovery), or any URL as JSON. Channels keep their secrets encrypted in the vault's envelope and never return them; each has a \"Send test\" button<\/li>\n<li>Added: rules decide when and where. Site-wide rules apply to every webhook; a webhook can inherit them, mute single ones, keep only its own, or switch notifications off. Filters: which attempt number, which HTTP codes (503, 5xx, 500-504, or no response), out-of-attempts vs not-retryable, trigger name patterns. A per-webhook quiet time and hourly or daily digests keep a noisy site quiet; a site-wide \"every success\" rule starts with a one-hour quiet time<\/li>\n<li>Added: every message is a template. Subject, title, body and a one-liner for SMS use the same <code>{{ path }}<\/code> placeholders as dynamic URLs, over the webhook, the event, the delivery (attempt, HTTP code, error, next try, log link), the mapped payload, the raw payload (<code>{{ args.0.email }}<\/code>) and the site, with modifiers such as <code>| truncate:120<\/code>, <code>| default:\"\u2014\"<\/code>, <code>| date:\"Y-m-d H:i\"<\/code> and <code>| json<\/code>. A live preview renders against the captured payload or a real delivery, a field picker inserts paths, and a linter flags paths that are not in the payload<\/li>\n<li>Added: Draft with AI. The rule editor asks the site's AI provider to write the message from the captured payload, and Build with AI can add a notification rule to a plan (\"and ping me on Slack if it fails\") \u2014 the same linter feeds unknown paths back to the model. New abilities: list_notification_channels, list_notification_rules, create_notification_rule, update_notification_rule, test_notification_rule \u2014 also reachable over MCP<\/li>\n<li>Added: a Sent tab with every notification, its status and a resend button; a bell on delivery-log rows that produced notifications; a health-bar warning when a channel keeps failing<\/li>\n<li>Added: the <code>fswa_delivery_event<\/code> action fires at every delivery state change with the full context (webhook, trigger, attempt, HTTP code, error, payloads), so your own code can react too; <code>fswa_notification_message<\/code> filters a rendered message before it is sent; <code>fswa_notification_channel_drivers<\/code> adds a channel type; <code>fswa_notification_http_args<\/code> tunes the outgoing request; <code>fswa_notification_inline_send<\/code> decides whether a synchronous webhook's notifications go out at the end of the same request (the default, so they never wait for a cron that may not run) or through cron like a queued delivery's<\/li>\n<li>Changed: builds export a webhook's notification rules (channels never travel); on import a rule whose channels do not exist here is created switched off and listed in the import problems<\/li>\n<li>Changed: database schema 2.5.0 adds the notification tables and two webhook columns<\/li>\n<\/ul>","raw_excerpt":"Describe an integration and the AI builds it \u2014 no API key needed. Outgoing webhooks from any WordPress or WooCommerce action, queued and retried.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/280953","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=280953"}],"author":[{"embeddable":true,"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/mateuszflowsystems"}],"wp:attachment":[{"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=280953"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=280953"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=280953"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=280953"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=280953"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/pl.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=280953"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}