NodeTrigger User Guide
Everything you need to know to filter, transform, and route your webhooks reliably. This guide covers the Webhook Router product — the current flagship feature of NodeTrigger.
Overview
NodeTrigger is a WordPress plugin that acts as a webhook router and proxy. Think of it as a smart middleman between your webhook providers and your downstream tools.
Here's what it does, in one sentence:
You give Stripe/Shopify/etc. one URL, and NodeTrigger verifies the source, evaluates your filter rules, optionally reshapes the data, and forwards it to one or more destinations — all durably and asynchronously.
How It Works (High-Level)
200 OK.{{dot.path}} templating.Quick Start
This assumes NodeTrigger is already installed and activated on your WordPress site. If you haven't installed it yet, follow the standard WordPress plugin installation process.
[nodetrigger] shortcode, or navigate to the NodeTrigger admin area if your administrator configured a dedicated page./nodetrigger/v1/route/3/a1b2c3d4...). This is what you'll paste into Stripe/Shopify as the webhook endpoint.Tip: Use Learning Mode (enabled on route creation). Leave the route active without filters, send a real webhook from your provider, then return to the route. The captured sample payload will be available to help you build filter rules visually.
Routes & Inbound URLs
A Route is the core unit of NodeTrigger. Each route represents one inbound webhook URL and its associated rules, filters, transformations, and destinations. You can create as many routes as you need.
Inbound URL Structure
Every route gets a unique URL in the following format:
https://yoursite.com/wp-json/nodetrigger/v1/route/{route_id}/{signature}
route_id— the numeric ID of your routesignature— a cryptographic HMAC derived from your account's secret key (generated automatically — you never create or manage it), unique per route
Good to know: The signature is generated for you — there's nothing to configure. Just create a route and copy its inbound URL. (If your account's secret key is ever reset by an administrator, existing inbound URLs stop working and you'll need to re-copy the new URL for each route — but this isn't something you do in normal use.)
Route Statuses
| Status | Meaning |
|---|---|
| Active | Route is accepting webhooks and processing them normally. |
| Inactive | Route is paused. Inbound webhooks are still accepted and logged (status paused) but are not forwarded to destinations. |
| Deleted (soft) | Marked as deleted but recoverable. No events are processed. |
Provider Source Verification
The inbound endpoint verifies the source signature according to the route's source type. Verification runs only when you've entered a source secret for the route — leave the secret blank and payloads are accepted unverified (true for every source type):
- Stripe — validates the
Stripe-Signatureheader using your Stripe webhook signing secret. - Shopify — validates the
X-Shopify-Hmac-Sha256header using your Shopify app secret. - Generic — for any other provider, validates a shared secret sent in the
X-NT-Secretheader against the secret you configure. Senders that can't set that header should leave the secret blank.
Failed verifications are logged with a red badge so you can see that a misconfigured source is hitting your endpoint — nothing is silently dropped.
Filter Rules
Filters let you decide which webhook events get forwarded and where they go. Without filters, every inbound event is forwarded to every destination (catch-all mode). With filters, you build precise conditional logic.
The Visual Rule Canvas
NodeTrigger provides a drag-and-drop canvas (powered by Drawflow) where you build rules visually:
Filter Operators
| Operator | Description | Example |
|---|---|---|
equals | Exact match | type equals checkout.session.completed |
contains | String/array contains value | email contains @gmail.com |
exists | Field is present (non-null) | metadata.order_id exists |
greater_than / less_than | Numeric comparison | amount > 10000 |
in_list | Value is in a predefined set | currency in [EUR, GBP, USD] |
regex | Regular expression match | customer_email matches /@company\.com$/ |
Every operator also has its negation — for example: not_equals, not_contains, not_exists.
Branch Logic: Match All vs. Match Any
A single filter can combine multiple conditions using either:
- Match ALL — every condition must be true (AND logic)
- Match ANY — at least one condition must be true (OR logic)
Branch Array (How Rules Run)
Rules are evaluated as a branch array — each branch is independent. The engine evaluates every branch against the incoming payload, and matching branches fan out to their respective destinations.
The filter engine is decoupled from the visual canvas. Even if you can't load the canvas (e.g., offline or CDN failure), your saved rules still execute normally — only the visual editor is affected.
Payload Transformation
Transformations let you reshape the payload before it reaches each destination. This is useful when different destinations expect different data schemas, or when you need to mask sensitive fields.
Transformation Types
| Type | How It Works |
|---|---|
Field Mapping (map) |
Pull a value from the source payload using dot-path syntax and assign it to a target field. Example: map data.object.customer_email → email. |
Static / Computed (static) |
Set a field to a fixed value, or use {{dot.path}} templates to interpolate values from the source. Example: "source": "{{event.type}}". |
| Redaction | Remove or mask sensitive keys. Actions: remove (delete the key) or mask (replace value with ***). |
Base Mode
You choose whether to start from:
all— begin with the full original payload, then map/redact fields.none— begin with an empty object, building only the fields you define.
Transform Resolution Order
When multiple transforms exist (global branch transform + per-destination transform), NodeTrigger resolves them as:
Destinations
A destination is any URL that receives forwarded webhook events. Each destination can have its own payload transformation.
Adding a Destination
Open a route and click on a Destination node in the canvas (or use the pencil icon to open the destination drawer). Enter the target URL — this is typically a webhook endpoint on your CRM, automation tool (n8n, Make), Slack channel, or custom API.
Sending to Multiple Destinations
A single route can fan out to any number of destinations. Each filter branch defines which destinations receive matching events. This means one inbound webhook can:
- Send all events to n8n for workflow processing
- Send only
checkout.completedevents to HubSpot - Send only high-value events (>€500) to a Slack channel
Legacy compatibility: Destinations stored as plain URL strings (without transform objects) are still accepted and work normally — the payload is forwarded as-is.
Fail-Safe Storage
NodeTrigger uses a double-storage strategy to guarantee no webhook event is ever lost, even if your database crashes mid-write.
How It Works
.htaccess deny rules and an unguessable path.200 OK as soon as the disk write succeeds, even if the database insert fails. The event is safe on disk.Storage Integrity
The dashboard includes a Fail-Safe Storage widget (🛡️) showing real-time status:
- Primary Database — whether the DB is reachable
- Durable JSON Log — whether the disk storage directory is writable
- Sync Integrity — percentage of events that exist in both DB and disk log. 100% = no drift.
Resurrection (Disaster Recovery)
If the database loses events (crash, corruption), an admin can run the "Resurrection" tool. It scans the durable log files, identifies any events missing from the database, and re-injects them into the queue. Events are de-duplicated by their event_uid, so duplicates are impossible.
Retention Policy
| Data | Retention | Cleanup Trigger |
|---|---|---|
| Disk log files | 30 days | Daily cron |
| Database events | 14 days | Daily cron |
| Unpaid/quota-hold events | Follows plan lifecycle | Plan-dependent |
| Raw payload history per route | 50 most recent | Automatic pruning |
Retries & Delivery
Two-Stage Processing
NodeTrigger separates ingestion from delivery to avoid keeping your providers waiting:
- Stage 1 (fast ACK): Payload received, stored,
200returned — typically within milliseconds. This satisfies Stripe's 10-second and Shopify's 5-second acknowledgment tolerances. - Stage 2 (async processing): A background worker picks up queued events, evaluates rules, transforms payloads, and delivers to destinations. This happens via a non-blocking loopback, with a 1-minute WP-Cron fallback if the loopback is dropped.
Retry Behavior
- Maximum attempts: 5 per delivery
- Backoff: Exponential (increasing delay between attempts)
- Concurrency guard: A MySQL named lock ensures only one worker drains the queue at a time — concurrent triggers can never double-forward an event
- Failed deliveries are visible in the event drawer with per-destination response details
Concurrency safety: Even if multiple webhooks arrive simultaneously and each triggers the worker, only one drainer runs at a time. The named lock prevents race conditions on shared hosting.
Flow Log & Replay
Event History
Every webhook received by NodeTrigger is logged with:
- A status badge (🟢 delivered, 🟡 retrying, 🔴 failed, grey = held)
- The full raw payload
- The transformed payload sent to each destination
- Each destination's HTTP response (status code + body)
- Timestamp and attempt count
Search
The flow log supports full-text search across stored payloads — useful for finding a specific event by customer ID, order number, or any field present in the JSON body.
Replay
You can replay any stored event at any time. The replay action re-inserts the original raw payload as a new queued event and processes it through the route's current rules (not the rules from when it originally arrived).
Replay uses current rules. If you've changed your filters since the event originally arrived, the replay will route according to the new configuration. The UI shows a yellow .ntr-replay-note warning to make this explicit.
Overview Dashboard
The Overview Dashboard provides a global control plane across all your routes. It displays:
- 30-day volume — total webhooks ingested across all routes
- Average ingestion latency — how fast your endpoint responds
- Active failures — deliveries currently retrying or permanently failed
- Destination success rate — what percentage of deliveries succeeded
- 24-hour traffic chart — visual representation of your webhook volume over the past day
- Live activity stream — real-time feed of events across all routes
Usage Widget
A sidebar widget shows your monthly consumption (used / quota, a progress bar, and the reset date). If your plan has a quota and you're approaching the limit, a badge will appear.
Security
Signature Verification
When a source secret is configured on a route, NodeTrigger validates the provider signature on every inbound webhook (with no secret set, payloads are accepted unverified):
- Stripe:
Stripe-Signatureheader verified against your webhook signing secret (configured per route in the source secret field). - Shopify:
X-Shopify-Hmac-Sha256header verified against your app secret. - Generic:
X-NT-Secretheader verified against your configured shared secret.
URL Signature
Each inbound URL contains a cryptographic signature derived from your account's automatically-generated secret key: hash_hmac('sha256', 'route_'.$id, account_secret_key). This means:
- URLs are unguessable — impossible to brute-force
- The key is provisioned for you; you never create or rotate it in normal use. If an administrator resets it, all existing URLs are invalidated at once.
SSRF Protection
NodeTrigger refuses to forward events to private, loopback, or reserved addresses (e.g., 127.0.0.1, 10.x.x.x, 192.168.x.x). This prevents attackers from using your route to probe your internal network.
Header Safety
When forwarding to destinations, NodeTrigger only passes a safe subset of headers — it never forwards authentication headers, cookies, or the Host header from the original inbound request.
Access Control
All administrative actions (creating routes, running resurrection, viewing events) require:
authentication (is_user_logged_in()) +
ownership verification (you can only interact with your own routes).
Plans & Quota
NodeTrigger's plan system is configured by your site administrator in Settings → Pricing / Plan. Here's what you need to know as a user.
Default Plan ("All-In")
| Feature | Value |
|---|---|
| Monthly webhooks | 100,000 / month |
| Equivalent daily average | ~3,300 events/day |
| Routes | Unlimited |
| All features | Filters, transforms, replay, fail-safe storage, signature verification — all included |
| Price | €29/month (excl. VAT) |
| Unpaid retention | 30 days (events held, not deleted) |
Quota Enforcement
Quota enforcement is role-based. Your administrator assigns a WordPress role (e.g., "Subscriber" or a custom role) that determines who is on the paid plan.
If no plan role is set, everyone can route freely with no quota enforcement. Administrators (manage_options capability) are always exempt from quota limits.
What Happens When You Hit Quota?
- Inbound webhooks continue to be stored (fail-safe still applies).
- Events are marked as
quota_holdinstead ofqueued— the worker does not forward them. - A once-per-month email alert is sent (if enabled by your admin).
- On the monthly reset, the counter resets to zero, and held events are automatically re-queued (their count deducts from the new month's quota).
Monthly Reset
The webhook counter resets on the first day of each month (driven by the daily maintenance cron). If WP-Cron is disabled on your site, the administrator needs to ensure it runs or the reset won't fire.
Rate Limiting
To protect your server from traffic spikes, NodeTrigger enforces a per-user rate limit on inbound webhooks.
| Details | |
|---|---|
| Limit type | Per-user, per-second ceiling (default 10 webhooks/second) |
| Response when exceeded | 429 Too Many Requests + Retry-After header |
| Provider behavior | Stripe, Shopify, Make, and n8n all respect 429 and retry — so no data is lost |
| Backend | Object-cache aware (Redis/Memcached/APCu) — no database write on the hot path |
| Configurable | Filterable via nt_router_rate_limit hook |
The rate limiter is intentionally designed to never add a database write to the webhook hot path. It uses in-memory counters (a persistent object cache like Redis/Memcached if available, otherwise APCu). If neither is present, rate limiting is simply not applied — the durable queue and single-worker lock already protect the server, so it never falls back to a database-backed counter. This keeps ingestion fast.
Best Practices
1. Start with Learning Mode
When creating a new route, enable Learning Mode and send a real webhook before building filters. The captured sample payload makes filter building much easier — you can click on actual fields instead of guessing JSON paths.
2. Use Specific Filters Before Catch-Alls
Build your most specific filter branches first, then use a catch-all (source → destination direct link) as the fallback. The branch array is evaluated in order, so more specific rules should come first.
3. Copy the Inbound URL Exactly
Your inbound URL is signed for you — there's no key to manage. Just copy each route's full URL into your provider exactly as shown. (The only thing that invalidates a URL is an administrator resetting your account's secret key, which isn't part of normal use; if it happens, re-copy the new URL for each route.)
4. Monitor the Fail-Safe Widget
The storage sync percentage should always be at 100%. If it drops below 100%, run the resurrection tool. A persistent desync may indicate a database issue that needs administrator attention.
5. Transform Payloads Per Destination
If you're forwarding the same event to both Slack and HubSpot, use per-destination transforms to reshape the data for each tool. Slack needs a text field; HubSpot expects properties.email — don't send the same shape to both.
6. Redact Sensitive Data
Use the redaction feature to mask or remove sensitive fields (card_number, customer_ip, etc.) before forwarding to third-party destinations. This is especially important when sending events to tools not under your direct control.
7. Check Flow Log Bedge Colors
A quick scan of green/yellow/red badges in your flow log tells you the health of your pipelines. A sudden spike in yellow (retrying) or red (failed) badges usually means a destination is down.
8. Respect the Retention Windows
Database events older than 14 days are pruned automatically. If you need long-term archives, export the flow log regularly or forward events to a persistent logging system as one of your destinations.
FAQ & Troubleshooting
My inbound URL stopped working. Why?
Check that the provider is calling the exact URL shown for your route — a truncated or edited URL will fail the signature check. In the rare case an administrator reset your account's secret key, every route's URL changes: open each route and copy the new inbound URL into Stripe/Shopify/etc.
I see a red badge with "invalid_url_signature". What does that mean?
A webhook hit your endpoint with an incorrect URL signature — usually the provider is configured with an old, truncated, or edited URL (or your account's secret key was reset by an admin). Re-copy the current inbound URL from the route and update your provider's webhook configuration. The event is logged so you know it happened — it's not silently dropped.
My webhooks are being received but not forwarded (grey badge).
Grey badges mean the event is held, not queued for delivery. Two possible reasons:
- Quota exceeded (
quota_hold) — you've hit your monthly limit. Events will be released on the next monthly reset. - Payment issue (
unpaid_hold) — your account is flagged as unpaid. Contact your administrator.
How do I know if my destination is down?
Check the event drawer for any delivery. Each destination shows its HTTP response. A 5xx status or connection timeout means the destination is unreachable. The retry badge (🟡) will appear until 5 attempts are exhausted.
Can I use NodeTrigger without WordPress?
No — NodeTrigger is a WordPress plugin. It requires a WordPress installation (self-hosted or managed WordPress hosting). It uses WordPress's REST API, cron system, and database layer.
What happens if WordPress cron is disabled on my site?
Two things: the monthly quota reset and the daily storage cleanup rely on WP-Cron. The async delivery loopback still works (it doesn't depend on cron), but the fallback worker (1-minute cron) won't fire. Your administrator should ensure WP-Cron is operational or use a real server cron to trigger wp-cron.php.
How fast is the ingestion pipeline?
Ingestion is designed to be fast — payload is written to disk and 200 is returned within a few milliseconds under normal conditions. However, raw throughput depends on your hosting. For high-volume scenarios (>10 events/second sustained), consider using an object cache (Redis) and a fast disk (SSD/NVMe).
Can I trigger NodeTrigger from my own code?
Yes. The inbound endpoint accepts any POST with a JSON body from any source. For non-Stripe/Shopify providers, select the Generic source type. To keep it verified, set a shared secret and have your sender include it in the X-NT-Secret header; leave the secret blank to accept payloads unverified.
What's the difference between Replay and Retry?
- Retry is automatic — the worker retries failed deliveries up to 5 times with exponential backoff.
- Replay is manual — you click "Replay" on a past event to re-process it through your current routing rules. The original raw payload is used, but the rules may have changed.
Are webhooks stored in my WordPress database?
Yes. Events are stored in the wp_nt_route_events table (and also in durable JSON log files on disk as a backup). This means webhook data counts toward your database size. Old events are automatically pruned (14 days DB, 30 days disk).