Webhooks fire once and disappear. If the endpoint fails, the event is gone — no log, no retry, no way to know what happened. What if your webhook infrastructure was as operable as any other API?
The plugin's REST API lets you:
The default WordPress webhook is a single HTTP call made inline during the PHP request. It fires once. If the receiving endpoint is down, rate-limited, or returns a 500, the delivery attempt produces nothing — no record, no alert, no rescheduled retry.
The event is not just undelivered. It is gone. There is no delivery log to inspect, no failure to surface, no mechanism to recover. Integration drift accumulates silently: your CRM, ERP, or automation platform diverges from WordPress reality while both systems report nominal health.
Even with retry and replay infrastructure in place, operating that infrastructure required logging into wp-admin. Useful for developers in the browser — not useful for monitoring systems, CI pipelines, or automation agents that need to query or act on delivery state without human intervention.
The failure modes that make this the default are covered in why WordPress webhooks silently fail in production — the structural problems that make the default model unreliable at scale.
The shift worth making is conceptual: stop thinking about webhooks as triggers and start treating them as an event pipeline with a lifecycle — persistence, dispatch, observation, recovery.
That lifecycle already existed inside the plugin. Every event is stored before dispatch. Failures enter a retry queue with exponential backoff. Successful deliveries remain available for manual replay. The full attempt history — timestamps, HTTP status codes, response bodies — is recorded per event.
The API was always there, powering the wp-admin UI. What changed is that it is now exposed. The same operations available in the WordPress admin panel — inspect logs, retry failures, replay events, check queue health, toggle webhooks — are now reachable via authenticated HTTP from any system that can make a web request.
This turns WordPress webhook infrastructure into something operable: scriptable, monitorable, automatable, and auditable without a browser.
Query the full history of every webhook delivery attempt. Filter by status (pending, success, error, retry, permanently_failed), by specific webhook ID, or by trigger name. Each log record includes the HTTP response code, response body, timestamp, attempt number, and the original payload that was sent.
Delivery stats are available separately — aggregate counts and success rates over a configurable time window — for dashboards and health checks.
Trigger a retry for any failed delivery log via the API. The retry uses the stored payload verbatim — the original WordPress action is not re-fired. This means you can recover a failed WooCommerce order event without creating a duplicate order, re-send a failed form submission without re-running the form hook, or recover any dead-letter event without side effects in WordPress itself.
Bulk retry accepts an array of log IDs, useful for recovering a batch of failures that occurred during a downstream outage.
Replay resends a previously successful delivery. Where retry is for failures, replay is for intentional resend — debugging a receiving system bug, rebuilding state after a migration, or testing a fix to an endpoint that was previously processing payloads incorrectly.
The X-Event-ID header carries the same UUID as the original event, giving receiving endpoints the information they need to deduplicate if needed.
GET /queue/stats returns pending, processing, completed, and failed counts. GET /health returns an aggregate overview — queue health, velocity metrics, recent failure rates. Both endpoints are lightweight enough to poll from a monitoring system or include in a dashboard.
Toggle any webhook on or off via the API. Useful for deployment workflows (disable webhooks during a database migration, re-enable after), for incident response (disable a misbehaving webhook without touching wp-admin), or for test environments that should not fire to production endpoints.
If you need the full webhook provisioning API — creating new webhooks, updating endpoint URLs and triggers, listing all registered webhooks, or deleting them — that is covered in the companion article: Create and Manage WordPress Webhooks Programmatically.
Base URL: https://your-site.com/wp-json/fswa/v1
Auth header: X-FSWA-Token: <token>
Filter by status, webhook ID, or trigger name. Requires read scope.
response_body surfaces the exact message returned by the downstream endpoint — the fastest way to identify whether the failure was a 503 outage, a 401 auth issue, or a 422 schema mismatch. attempt_history shows the full retry trail per event, including timestamps and per-attempt HTTP codes.
Aggregate success/failure counts over a time window. Requires read scope.
permanently_failed is the number to alert on. avg_duration_ms tracks endpoint latency over time — a gradual increase signals a degrading downstream service before it starts hard-failing.
Re-sends the stored payload without re-triggering the WordPress action. Requires operational scope.
job_id is the queue entry for this retry. Use it to correlate with GET /queue/stats output when debugging whether the job was picked up by the dispatcher.
Recover a batch of failures at once. Requires operational scope.
skipped counts IDs that were not in a retriable state — already pending, processing, or succeeded. Non-zero means your ID set included events that didn't need recovery. Check this in automated scripts to detect stale IDs.
Resend a previously delivered event. Carries the original X-Event-ID for deduplication. Requires operational scope.
Poll queue depth and system health from a monitoring system or uptime dashboard. Requires read scope.
queue.due_now is the most actionable field for alerting — non-zero means jobs are overdue and WP-Cron may not be running. observability.queue_stuck flags this explicitly. velocity.last_hour and avg_duration_ms feed directly into dashboard panels without additional aggregation.
Disable during deployments or incidents. Re-enable without touching wp-admin. Requires operational scope.
Returns the full updated webhook state. Always check is_enabled in your script rather than assuming the toggle succeeded — this is the confirmation.
A WooCommerce store sends order events to HubSpot via webhook. HubSpot has a 9-minute API outage. Forty-three order events fail to deliver during that window.
The team gets a Slack alert from their monitoring system: webhook failure rate spiked. Someone logs into wp-admin, navigates to the delivery log, confirms the failures, then starts clicking "retry" individually. For 43 events. Then checks back manually to confirm each one succeeded.
The same operations in the Symfony CLI wrapper someone wrote six months ago:
HubSpot's status page shows all-clear. The monitoring system calls a recovery script automatically — no human in the loop:
The same pattern works for more complex scenarios: disable a webhook before a database migration, replay a specific order event to test a receiving-side bug fix, query queue depth every 60 seconds from a Grafana datasource. All from outside WordPress, with no wp-admin dependency.
Every webhook event follows this path. The REST API provides programmatic access to every node in the lifecycle — logs, retry queue, replay, health.
The API surfaces the full delivery history at every node. GET /logs queries any status. GET /queue/stats exposes queue depth. GET /health aggregates across the whole pipeline. Retry and replay endpoints operate directly on stored payloads — no WordPress action re-fires, no side effects.
See the async webhook architecture post for how dispatch is decoupled from the PHP request cycle.
Tokens are issued per-site from the plugin settings panel. Each token carries one of three scopes. Use the minimum scope required for the operation.
Scope Header Permitted Operations
─────────────────────────────────────────────────────────────────────────
read X-FSWA-Token: <token> GET /logs
GET /logs/stats
GET /queue/stats
GET /health
operational X-FSWA-Token: <token> All read operations, plus:
POST /logs/{id}/retry
POST /logs/bulk-retry
POST /logs/{id}/replay
POST /webhooks/{id}/toggle
full X-FSWA-Token: <token> All operational operations, plus:
webhook configuration endpoints
Monitoring systems and dashboards should use read tokens. Recovery scripts and CI pipelines should use operational. No token should ever carry more scope than its use case requires.
Tokens are revocable independently. If an operational token is exposed in a script or log, revoke it from the plugin settings without affecting other tokens or the site's webhook configuration.
When the API was first exposed internally, the expected use cases were monitoring scripts and CI pipelines. The less expected use case emerged almost immediately: AI coding agents.
Claude Code, Cursor, and similar agents can invoke the API directly when given a token with read or operational scope. An agent debugging an integration failure can inspect the delivery log, examine the response body, identify the failure pattern, and retry the affected events — without a developer opening a browser or logging into wp-admin.
This is a meaningful shift in how webhook infrastructure gets operated. An on-call agent can run a morning health check: GET /health, surface any permanently failed events, identify the affected webhooks, check if the downstream service is back up (via a separate status check), and issue a bulk-retry if appropriate. The human reviews the action log, not the raw data.
The prerequisite for this to work is exactly what the API provides: a machine-readable interface to delivery state, with idempotent operations that have clear, bounded effects.
The REST API is the right approach when:
If you are running a simple WordPress site with one or two webhooks and low event volume, the wp-admin UI is probably sufficient. The API adds value when the operational complexity of managing webhook delivery outgrows what a browser-based interface can handle efficiently.
Flow Systems Webhook Actions includes the full REST API described in this article. Every endpoint, authentication model, and response format is documented in the REST API reference. The plugin is free and open source, distributed via WordPress.org. See the plugin landing page for the full feature overview.
POST /wp-json/fswa/v1/logs/{id}/retry re-sends a failed delivery using the stored payload without re-triggering the original WordPress action — so retrying a failed WooCommerce order event won't create a duplicate order.POST /logs/bulk-retry with an array of log IDs, which is useful for recovering a batch of failures from a downstream outage in a single request. Both endpoints require a token with operational scope.
GET /wp-json/fswa/v1/logs?status=error to list failed deliveries, or GET /logs/stats?days=7 for aggregated counts. You can filter by webhook_id, trigger_name, or status (pending, success, error, retry, permanently_failed).read scope.
X-FSWA-Token header. Tokens are issued per-site from the plugin settings panel and carry one of three scopes:read, plus retry, bulk retry, replay, and webhook toggle.operational, plus webhook configuration endpoints.read, recovery scripts operational.