TL;DR: n8n has no WordPress trigger node, so most people poll. That is the right default for posts and orders — and it is structurally blind to most WordPress events.
- The official WordPress node is actions-only — create, update, retrieve. It does not listen.
- Polling works where the REST API exposes a queryable, date-filterable list. Posts qualify. Most things do not.
/wp/v2/usershas no date filter at all. You can find new users; you cannot detect an updated one.- Polling reads state. A value that changes and changes back between two polls never happened.
- The "webhooks don't fire because WP-Cron needs traffic" objection is real, and has four separate fixes — one of which needs no cron at all.
/ Objection
Why do n8n users poll WordPress instead of receiving webhooks?
Because the platform pushes them that way, and because the usual alternative has a bad reputation.
n8n's official WordPress node is an action node. It creates posts, updates posts, retrieves posts and users. There is no WordPress trigger node — nothing in the box listens for something happening on your site. So the natural shape of a WordPress workflow in n8n is a Schedule Trigger followed by an HTTP Request or the WordPress node, asking "anything new since last time?"
The second reason is the one that comes up in every thread: WordPress webhooks are widely believed to be unreliable, because WP-Cron only runs when somebody visits the site. On a quiet site a queued delivery can sit for hours. That belief is correct about the default, and we will deal with it properly further down — but it is worth separating the two questions. "Is push reliable here?" is a configuration question with a known answer. "Can polling see this event at all?" is a structural one, and it does not have an answer.
Credit where it is due: for the two cases people usually build first, polling is genuinely the better default. It needs nothing installed on the WordPress side, which matters enormously when the site belongs to a client who will not let you add a plugin. It is stateless and self-healing — if n8n is down for an hour, the next poll simply picks up a wider window. And it backfills: a webhook only ever fires forward, so a workflow built today knows nothing about yesterday.
| WordPress event | Pollable? | Why |
|---|---|---|
| New post published | Yes | /wp/v2/posts supports after and modified_after |
| Post edited | Yes | orderby=modified plus modified_after |
| New WooCommerce order | Yes | /wc/v3/orders exposes date filters |
| New user registered | Partly | orderby=registered_date only — no date filter |
| User profile updated | No | no modified field, no date filter, no ordering |
| Contact Form 7 submission | No | nothing is stored without an add-on |
| Comment approved | No | status transition leaves no queryable trace |
| Any custom do_action | No | no endpoint exists to poll |
/ Limits
What can polling not see?
Polling is a query against a list. It works when three things are true at once: the thing you care about is stored, that store has a REST endpoint, and that endpoint can be filtered or ordered by a date that changes when the thing happens. Posts satisfy all three, which is why every popular n8n WordPress template is built on them.
Users do not. The List Users endpoint accepts context, page, per_page, search, exclude, include, offset, order, orderby, slug, roles, capabilities, who and has_published_posts. There is no after, no modified_after, and the permitted orderby values stop at registered_date. You can therefore find users who registered since your last run, and you have no mechanism whatsoever for finding a user whose profile changed:
n8n — the poll that cannot be written
// Works: new users since the last run. GET /wp-json/wp/v2/users?orderby=registered_date&order=desc&per_page=20 // then discard everything older than $lastRun in a Code node // Does not exist: users whose profile changed since the last run. GET /wp-json/wp/v2/users?modified_after=2026-09-08T00:00:00 // ^ modified_after is silently ignored. You get page 1 of all users. // The only remaining option is a full-table diff, every run: // fetch every user, hash each record, compare against a stored hash. // 10,000 users x every 5 minutes = 2.88M records/day to notice one edit.
That last line is the honest cost of forcing a poll onto an event that was never designed to be polled. It is not that it is impossible — it is that the workaround is a full-table diff on a schedule, and it scales with the size of your site rather than with the number of things that actually happened.
The deeper problem is that polling reads state, not events. A poll tells you what is true now. It cannot tell you what happened in between. If a post is published and then unpublished inside your five-minute window, your workflow never sees it. If an order moves from processing to completed to refunded between two runs, you observe one transition and miss two. For an audit trail, a notification, or anything where the transition is the business event, that gap is the whole problem.
And then there is the class of events that leaves no trace at all. Contact Form 7 does not store submissions — without an add-on such as Flamingo, the data exists only for the duration of the request that carried it. There is nothing to poll, at any interval, ever. The same is true of every custom do_action in every plugin on the site: those are the events that make WordPress extensible, and none of them has an endpoint.
Polling answers "what is true now?". A webhook answers "what just happened?". Most WordPress events are the second question, and only the first one has an endpoint. — the line between the two approaches
/ Cron
But don't WordPress webhooks fail because WP-Cron needs traffic?
This is the strongest argument against push and it deserves a straight answer rather than a defence. WP-Cron is not a scheduler — it is a task list checked during page loads. If a plugin accepts an event, queues it, and waits for WP-Cron to drain the queue, then on a site with no visitors nothing is drained. The event is not lost, but it is not delivered either, and "delivered eventually, when someone happens to browse the site" is not a property you can build an automation on.
There are four ways out of it, and they are worth knowing in order of how little work they take:
- Send synchronously. The delivery fires inline, inside the request that triggered it, and never touches cron at all. It costs a little latency on that one request and it works on any site from the moment it is switched on. This is the correct default on a low-traffic site, and it is exactly what the objection is asking for.
- Let Action Scheduler take the queue. If Action Scheduler is present — and it is on every WooCommerce site, because WooCommerce ships it — a queue can be handed to it instead of WP-Cron, which gives persistent job tracking and far better behaviour under load. On a WooCommerce store the problem tends not to arise in the first place.
- Point a real cron at it. A token-authenticated REST endpoint that a system crontab or a free external pinger hits on a fixed schedule turns "when someone visits" into "every sixty seconds". External cron services covers the free options.
- Use a managed pinger. The same thing without a crontab, for people who do not have shell access to the server.
The distinction that matters for an n8n user evaluating this: the failure mode above is a property of queued delivery on an idle site, not of webhooks. Synchronous delivery has no cron in the path. Once that is understood, the reliability question stops being "can WordPress push?" and becomes the ordinary question you would ask of any sender — does it retry, can you see what it sent, and can you replay it when the receiving workflow had a bug?
/ Choosing
Which should an n8n workflow actually use?
Both, in different places — and on the push side it matters a great deal what is doing the pushing. A hand-rolled wp_remote_post inside a hook is a webhook in the same sense that a curl in a loop is a delivery system: it works until the receiving end is down. So the honest comparison has three columns, not two.
| Concern | Polling from n8n | Raw do_action + wp_remote_post | Webhook Actions |
|---|---|---|---|
| Setup on the WordPress side | Nothing at all — works on sites you do not control | A snippet per event, maintained by you | Install once, then wire events up in wp-admin |
| Events it can observe | Only what has a date-filterable REST endpoint | Any do_action — one snippet each | Any do_action, including custom plugin events |
| Transitions and one-off events | Missed if they resolve between two polls | Delivered when they happen | Delivered when they happen |
| Latency | Half the poll interval on average | Immediate — inside the request | Immediate synchronously, or seconds when queued |
| Load when nothing happens | Every interval, all day, forever | None | None |
| Delivery on an idle, low-traffic site | Unaffected — n8n does the polling | Inline works; a WP-Cron queue never drains | Synchronous mode, or Action Scheduler / real cron |
| Retrying a failed delivery | Not needed — the next poll re-reads | None — wp_remote_post fires once | Automatic on 5xx and 429, exponential backoff, 5 attempts |
| Recovering from a broken workflow | Re-run against an earlier window | The payload is gone | Every request and response logged; replay the original event |
| Backfilling history from before you set it up | Widen the date window and re-run | Not possible — fires forward only | Not possible — fires forward only |
Read the last three rows together and the practical rule falls out. Polling recovers by re-querying, so its resilience is free. A webhook fires once, so everything depends on whether the sender kept the payload — which is why the middle column is not a cheaper version of the third one, it is a different reliability class. A sender that fires and forgets genuinely is worse than a poll. A sender that stores every payload, retries on 5xx and 429, and lets you re-send the original event after you fix the workflow is strictly better, because it also sees the events polling cannot reach.
So: poll for posts and orders on sites you do not control, and for backfilling anything that happened before the integration existed. Push for form submissions, user changes, status transitions and anything custom — and use something that keeps a delivery log, rather than a snippet that throws the payload away the moment n8n returns a 500.
Seeing it run beats reading about it. The live preview boots a throwaway WordPress with Webhook Actions already installed and demo deliveries sitting in the log — no signup, nothing left on your machine afterwards.