/ Article — REST API

Create and Manage WordPress Webhooks Programmatically

In most WordPress setups, webhooks are hardcoded in PHP, configured manually in a plugin UI, and tightly coupled to deployments. If you want to change an endpoint, disable an integration, or create webhooks dynamically — you edit code, redeploy, and hope nothing breaks.

Modern systems don't work like this. You control integrations via API.

/ The Problem

The Missing Webhook Control Layer

Most articles about WordPress webhooks focus on sending them — how to fire an HTTP request when an order is placed, a form is submitted, or a user registers. The sending part is well-covered.

The missing piece is the control layer: there is no standard way to create webhooks remotely, update their endpoints, toggle integrations on and off, or manage them across multiple environments without logging into each site individually.

Without API control, webhooks are static. You write them once in PHP and they stay there until someone edits the code. When an endpoint changes, you deploy. When a staging environment should not fire to production, you comment out code. When a client's integration needs to be paused, you log in and click through the admin UI.

This is the difference between hardcoded integrations and API-driven infrastructure. The delivery layer — retry, replay, monitoring — is one part of the picture. The control layer is the other: how you create and manage what gets sent, and where.

/ Create

Create a Webhook via REST API

Base URL: https://your-site.com/wp-json/fswa/v1
Auth header: X-FSWA-Token: <token> — requires full scope

A webhook needs two things: a name and an endpoint URL. Everything else is optional — triggers (which WordPress actions fire this webhook), auth header (sent with each delivery), and whether it starts enabled.

curl — create a webhook
curl -X POST \ "https://your-site.com/wp-json/fswa/v1/webhooks" \ -H "X-FSWA-Token: your-full-scope-token" \ -H "Content-Type: application/json" \ -d '{ "name": "CF7 → n8n", "endpoint_url": "https://n8n.yourdomain.com/webhook/abc123", "triggers": ["wpcf7_mail_sent"], "is_enabled": true }'
response — JSON
{ "id": 7, "name": "CF7 → n8n", "endpoint_url": "https://n8n.yourdomain.com/webhook/abc123", "auth_header": null, "is_enabled": true, "triggers": ["wpcf7_mail_sent"], "created_at": "2026-03-25 11:04:38", "updated_at": "2026-03-25 11:04:38" }

id is the webhook identifier used in all subsequent operations. triggers maps to WordPress action hook names — use GET /wp-json/fswa/v1/triggers to browse all available hooks grouped by category (WooCommerce, Contact Form 7, WordPress core, etc.).

With auth header

If the receiving endpoint requires authentication, pass it as auth_header. It will be sent as the Authorization header with every delivery.

curl — with authorization
curl -X POST \ "https://your-site.com/wp-json/fswa/v1/webhooks" \ -H "X-FSWA-Token: your-full-scope-token" \ -H "Content-Type: application/json" \ -d '{ "name": "WooCommerce → HubSpot", "endpoint_url": "https://hooks.example.com/crm/orders", "auth_header": "Bearer hs_live_secret_abc123", "triggers": ["woocommerce_order_status_completed"], "is_enabled": true }'

JavaScript — fetch

JavaScript — create webhook
const response = await fetch( 'https://your-site.com/wp-json/fswa/v1/webhooks', { method: 'POST', headers: { 'X-FSWA-Token': 'your-full-scope-token', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'CF7 → n8n', endpoint_url: 'https://n8n.yourdomain.com/webhook/abc123', triggers: ['wpcf7_mail_sent'], is_enabled: true }) } ); const webhook = await response.json(); // webhook.id — use this for all subsequent operations
/ List & Inspect

List and Inspect Webhooks

Listing webhooks requires only a read scope token — you don't need write access to audit what's configured. This is the right token scope for monitoring scripts, dashboards, and read-only tooling.

curl — list all webhooks
curl -X GET \ "https://your-site.com/wp-json/fswa/v1/webhooks" \ -H "X-FSWA-Token: your-read-scope-token" # Add ?only_enabled=true to return only active webhooks
response — JSON (array)
[ { "id": 5, "name": "Order sync — HubSpot", "endpoint_url": "https://hooks.example.com/crm/orders", "is_enabled": true, "triggers": ["woocommerce_order_status_completed"], "created_at": "2026-02-10 08:30:00", "updated_at": "2026-03-24 14:15:22" }, { "id": 7, "name": "CF7 → n8n", "endpoint_url": "https://n8n.yourdomain.com/webhook/abc123", "is_enabled": true, "triggers": ["wpcf7_mail_sent"], "created_at": "2026-03-25 11:04:38", "updated_at": "2026-03-25 11:04:38" } ]

Get a single webhook

curl — get webhook by ID
curl -X GET \ "https://your-site.com/wp-json/fswa/v1/webhooks/7" \ -H "X-FSWA-Token: your-read-scope-token"
/ Update & Control

Update and Control Webhooks

Update a webhook

PATCH /webhooks/{id} accepts any subset of fields — you only send what you want to change. Requires full scope.

curl — change endpoint URL
curl -X PATCH \ "https://your-site.com/wp-json/fswa/v1/webhooks/7" \ -H "X-FSWA-Token: your-full-scope-token" \ -H "Content-Type: application/json" \ -d '{ "endpoint_url": "https://n8n.yourdomain.com/webhook/production-xyz" }'
curl — add a trigger to an existing webhook
curl -X PATCH \ "https://your-site.com/wp-json/fswa/v1/webhooks/5" \ -H "X-FSWA-Token: your-full-scope-token" \ -H "Content-Type: application/json" \ -d '{ "triggers": ["woocommerce_order_status_completed", "woocommerce_order_status_refunded"] }'

Toggle a webhook on / off

POST /webhooks/{id}/toggle flips the is_enabled flag. No request body required. This endpoint needs only operational scope — not full.

Toggle requires only operational scope. This is intentional: CI pipelines and deployment scripts that need to pause integrations during a migration don't need full write access — they just need to flip a switch. Create a dedicated operational token for these workflows.

curl — toggle webhook
curl -X POST \ "https://your-site.com/wp-json/fswa/v1/webhooks/7/toggle" \ -H "X-FSWA-Token: your-operational-token" # No request body needed. Flips current is_enabled state.
response — JSON
{ "id": 7, "name": "CF7 → n8n", "endpoint_url": "https://n8n.yourdomain.com/webhook/abc123", "is_enabled": false, "triggers": ["wpcf7_mail_sent"], "updated_at": "2026-03-25 11:44:09" } // Always check is_enabled in the response — that is your confirmation.

Delete a webhook

Remove a webhook entirely. Requires full scope. This cannot be undone — the webhook and its configuration are deleted permanently.

curl — delete webhook
curl -X DELETE \ "https://your-site.com/wp-json/fswa/v1/webhooks/7" \ -H "X-FSWA-Token: your-full-scope-token"
response — JSON
{ "deleted": true, "id": 7 }
/ Use Cases

Real-World Use Cases

CI/CD and environment setup

During a deployment pipeline, create environment-specific webhooks automatically. Point staging to a test endpoint, production to the live one. Disable all webhooks before a database migration, re-enable after it completes. No one logs into wp-admin during a deployment window.

bash — CI/CD deployment script
SITE="https://your-site.com/wp-json/fswa/v1" TOKEN="${FSWA_FULL_TOKEN}" # Disable all webhooks before migration WEBHOOK_IDS=$(curl -s "${SITE}/webhooks?only_enabled=true" \ -H "X-FSWA-Token: ${TOKEN}" \ | jq '[.[].id]') for id in $(echo "${WEBHOOK_IDS}" | jq '.[]'); do curl -s -X POST "${SITE}/webhooks/${id}/toggle" \ -H "X-FSWA-Token: ${TOKEN}" > /dev/null done # ... run migration ... # Re-enable after migration completes for id in $(echo "${WEBHOOK_IDS}" | jq '.[]'); do curl -s -X POST "${SITE}/webhooks/${id}/toggle" \ -H "X-FSWA-Token: ${TOKEN}" > /dev/null done

SaaS integrations

If you're building a product that integrates with customer WordPress sites, you can register webhooks programmatically during onboarding. No manual configuration. No asking customers to copy-paste an endpoint URL into a form. The webhook is created, the trigger is registered, the integration is live — from a single API call in your onboarding flow.

Automation platforms — n8n, Make, Zapier

When building flows in n8n or similar platforms, endpoint URLs change as you move between test and production workflows. Instead of updating WordPress manually every time, your automation can call the PATCH endpoint to update the webhook URL when a workflow is promoted to production. The endpoint stays in sync with the automation, not the other way around.

For a practical example of sending CF7 form data to n8n, see the Contact Form 7 → webhook example.

Agency / multi-site management

Managing webhooks across ten client sites means ten separate wp-admin logins to push a configuration change. With API control, one script handles all of them. Standardize webhook configuration across environments, audit what's enabled on each site, roll out endpoint changes without browser tabs.

/ Architecture

Two Layers of a Complete Webhook System

Webhook infrastructure has two distinct concerns. Most systems handle only one of them.

Control Layer (this article)            Delivery Layer (linked below)
──────────────────────────────────      ─────────────────────────────────
POST   /webhooks           create       retry   POST /logs/{id}/retry
GET    /webhooks           list         replay  POST /logs/{id}/replay
GET    /webhooks/{id}      inspect      monitor GET  /health
PATCH  /webhooks/{id}      update               GET  /queue/stats
POST   /webhooks/{id}/toggle on/off     inspect GET  /logs?status=error
DELETE /webhooks/{id}      remove       recover POST /logs/bulk-retry

The control layer (this article) handles what exists: which webhooks are registered, where they point, and whether they're active. The delivery layer handles what happened: did events reach their destination, what failed, how to recover.

Together they give you webhooks that are both reliable and controllable. The delivery layer is covered in the companion article: WordPress Webhooks REST API: Retry, Replay and Monitor Events Programmatically.

Flow Systems Webhook Actions exposes both layers via the same authenticated REST API. The full reference — all endpoints, request/response schemas, and scope requirements — is in the API documentation.

/ Authentication

Authentication and Token Scopes

Tokens are issued per-site from the plugin admin panel under API Tokens. Each token carries one scope. Use the minimum scope required for the operation — don't give a monitoring script write access.

Scope Permitted Operations Typical Use
read GET /webhooks, GET /webhooks/{id}, GET /logs, GET /health, GET /queue/stats Monitoring, dashboards, auditing
operational All read operations, plus: POST /webhooks/{id}/toggle, POST /logs/{id}/retry, POST /logs/bulk-retry, POST /logs/{id}/replay CI/CD pipelines, recovery scripts
full All operational operations, plus: POST /webhooks (create), PATCH /webhooks/{id} (update), DELETE /webhooks/{id} Onboarding scripts, provisioning

The token is passed in the X-FSWA-Token header. Alternatively, use Authorization: Bearer <token> if your HTTP client expects a Bearer auth format, or append ?api_token=<token> as a query parameter for tools that don't support custom headers.

Tokens are independently revocable. If a token is exposed in a log or a script, revoke it from the plugin settings without disrupting other integrations.

/ FAQ

Common questions

Yes. POST /wp-json/fswa/v1/webhooks creates a new webhook from a JSON payload. Required fields are name and endpoint_url. Optional fields include triggers (array of WordPress action hook names), is_enabled (boolean, defaults to true), and auth_header (authorization value sent with each delivery).

Requires a token with full scope. The response includes the webhook id, which you use for all subsequent update, toggle, and delete operations.
Use PATCH /wp-json/fswa/v1/webhooks/{id} to update any field — endpoint URL, name, triggers, auth header. All fields are optional, so you can patch only what changed. Requires full scope.

To enable or disable without a full update, use POST /webhooks/{id}/toggle — this only requires operational scope, making it safe for CI pipelines. To remove a webhook entirely, use DELETE /webhooks/{id} with a full scope token.
Creating, updating, and deleting webhooks requires a full scope token. Toggling a webhook on or off requires only operational scope — useful for CI/CD pipelines that pause integrations during deployments without needing full write access. Listing and inspecting webhooks requires only read scope.

Use the minimum scope required. Monitoring scripts should use read. Deployment scripts that only toggle webhooks should use operational. Provisioning scripts that create and configure webhooks need full.
GET /wp-json/fswa/v1/webhooks with an X-FSWA-Token header carrying a read scope token. The response is a JSON array where each item includes id, name, endpoint_url, is_enabled, triggers, created_at, and updated_at.

Add ?only_enabled=true to filter to active webhooks only. To get a single webhook by ID, use GET /webhooks/{id}.