Know the moment a webhook fails.
Retries handle the flaky minute. This handles the endpoint that stays down at 3 a.m. — a Slack card, an email, a text or a page the moment a delivery gives up, and the all-clear when it recovers. Rules decide when and where. Every message is a template you can preview against a real payload, or let the AI draft.
Thirteen destinations. Secrets never shown again.
A Slack URL or a bot token is stored encrypted in the same envelope the Credentials Vault uses and is write-only over the API — the channel card shows its last four characters, nothing more. Every channel has a Send test button, and one that keeps failing is called out in the plugin's health bar.
A channel, a rule, a message.
Somewhere a message can go
An inbox, a Slack or Discord channel, a Telegram chat, a phone number, a PagerDuty service. Paste the URL or token once; the plugin keeps it encrypted. Click the paper plane to see a test land.
When, and to whom
Pick the event, optionally narrow it — attempt 1 only, 5xx only, this trigger only — and pick the channels. Site-wide rules apply to every webhook; a rule can also belong to one webhook alone.
- Attempt failed — One try failed. Filter by attempt number and HTTP code, or you will hear about every retry.
- Retry scheduled — Same moment, plus when the next try is.
- Permanently failed — The plugin has stopped trying — out of attempts, or a 4xx that will never succeed. This is the one to page on.
- Recovered — Delivered after earlier failures. The all-clear.
- Delivered — Every success. Pair it with a daily digest, never raw.
- Skipped — Conditions did not pass. Usually expected.
What lands in Slack
A card with the webhook, the trigger, the attempt, the HTTP code, the error and a button straight to the delivery log. Email gets a formatted mail, SMS the one-liner, PagerDuty an incident. Same message, each channel's native shape.
Only the noise you asked for.
Per webhook — inherit, mute one, or go custom
Filters — attempt, HTTP code, reason, trigger
Every message is a template. The AI can write it.
Subject, title, body and a one-liner for SMS use the same {{ path }} placeholders as dynamic URLs —
over the webhook, the delivery, the mapped payload, the raw hook arguments and the site — with modifiers such as | truncate:120, | default:"—" and | date:"Y-m-d H:i".
An Insert field picker lists every path in your captured payload with a sample value. A linter flags a path that is not there before you save.
Draft with AI writes the first version from that payload.
Order {{ payload.order.id | default:"?" }} did not reach HubSpot. Customer: {{ payload.billing.email }} Attempt {{ delivery.attempt }} of {{ delivery.max_attempts }} HTTP {{ delivery.http_code | default:"no response" }} {{ delivery.error_message | truncate:160 }} {{ delivery.log_url }}
Fields — start from the default, not a blank
Preview — rendered as you type
Field picker — every path, with a sample
Linter — a path the payload lacks, before you save
One hook for the whole lifecycle.
The dispatcher fires fswa_delivery_event at every state change with the full context, so your own code can react without querying anything back. fswa_notification_message rewrites a rendered message, fswa_notification_channel_drivers adds a channel type, and five abilities let Build with AI — or Claude Code over MCP — create and test rules.
add_action( 'fswa_delivery_event', function ( $event, $ctx ) { if ( $event !== 'permanently_failed' ) return; // $ctx: webhook, trigger, attempt, http_code, error_message, // response_body, payload, original_payload, log_id … my_incident_tool()->open( $ctx['webhook']['name'], $ctx['error_message'] ); }, 10, 2 );
Before you switch it on.
The next failed delivery tells you itself.
Free plugin, five-minute setup, nothing to subscribe to.