Advanced Webhook Handling

A busy CRM talks constantly. Every tag applied, every field edited, every new contact can fire a webhook at your site. On most membership plugins each of those notifications forces a full WordPress load, and a bulk edit across thousands of contacts can take the whole site down while it grinds through them.

Torii handles the flood differently. Each notification is acknowledged within milliseconds and written down, and the actual work of applying changes happens in the background, at a pace your server sets. You can watch every change and its outcome in one place.

What background handling buys you

  • Bulk edits don't stall your site. Import or mass-edit thousands of contacts and your front end never notices. The notifications queue up and apply in the background.
  • Nothing is refused for being busy. Every notification gets an instant receipt, so your CRM never times out and never retries against a loaded server.
  • You can see the work. A queue of received changes, with a status for each one, replaces the old guesswork of "did that webhook arrive?"
  • Members see changes quickly. When a logged-in member's own record changes in the CRM, their updates jump the line and apply during their session.

What happens to a webhook

When a watched contact changes in your CRM, the CRM notifies a small PHP file at your site's root, wp-torii-webhook.php. The file is generated by Torii and stands alone: it loads no WordPress, no themes, no plugins. Its whole job is to check the secret key, read the notification, write one row into the webhook queue, and answer. That takes a couple of milliseconds.

A background worker then drains the queue: it takes rows in the order they arrived, replays each one through the same processing path a live webhook would take, and updates the member, tags, and fields accordingly. Every result is recorded with its outcome.

Two hard limits protect the gate itself: it only accepts POST requests, and it refuses any request body over 1 MB. Real CRM notifications are a few kilobytes; anything larger is an attack, not a webhook.

Turning it on

  1. Open Membership → Settings → Webhooks and enable the webhook gate.
  2. Click generate. Torii writes wp-torii-webhook.php to your site's root and fills in the gate URL.
  3. Run the healthcheck next to the URL. It confirms the file is reachable, the database connection works, and the queue table is current.
  4. Connect, or reconnect, your CRM. The connector registers its notification channels with your CRM automatically, pointed at the gate.

If your host forbids the plugin from writing to the site root, Torii shows the complete file contents in a copy/paste panel instead. The panel asks for confirmation before revealing anything, because the generated file embeds your database credentials: treat a copy of it the way you treat wp-config.php. Paste it wherever you can place the file, then set the gate URL to match its public location. The URL and the placement are configured separately, so a host that only serves the file from an unusual path still works.

Every reply looks like success

The gate answers every request with the same instant success response, whether the key was valid, the row was recorded, or anything in between failed. This is deliberate, and it is not a bug to report.

Two reasons. First, anything else invites retry storms: a CRM that receives an error will resend the same notification over and over, which multiplies the very load the gate exists to absorb. Second, varied responses are an oracle: an attacker probing webhook URLs learns whether a key is valid from the difference in replies. Uniform replies teach them nothing.

That silence costs you nothing. When the gate rejects or records a request, the reason goes to your server's error log, and everything that reaches the queue is visible with its outcome on the webhook settings screen.

Watching the work

The webhook settings screen shows the queue by connector: how many changes are waiting, in progress, or finished, and how old the oldest waiting change is. A manual drain button flushes waiting rows immediately, which is useful after a big import, before switching connectors, or any time you don't want to wait for the next pass.

Each row in the queue carries a status:

StatusMeaning
WaitingThe change was received and recorded. It will be applied on the next background pass.
ProcessingA worker has the row and is applying it now.
DoneThe change was applied successfully.
IgnoredThe change was received and deliberately not applied, for example an operation your site is configured to skip.
FailedSomething went wrong while applying. The row is kept with its outcome for diagnosis.

A few behaviors underneath those statuses are worth knowing.

The pace is batched, not instant. The background worker runs about once a minute and takes up to 50 rows per pass, looping until the queue for your active connector is empty. Under sustained flood the queue behaves like a dam: the water rises, then drains at a steady rate.

Browsing members jump the line. When a logged-in member loads any page on your site, Torii drains that member's own waiting rows during the request. The member who most needs the change to land is the one who gets it fastest.

Stuck rows unstick themselves. If a worker dies mid-drain, its rows would otherwise sit in processing forever. After five minutes, a stuck row returns to waiting and is picked up by the next pass. Nothing needs your intervention.

Repeat notifications collapse, when that is safe. Some CRMs send the full state of a contact in every notification, so ten rapid edits mean ten notifications where only the last matters. For those connectors, Torii keeps just the newest notification per contact and operation and drops the superseded ones. Other CRMs, Zoho among them, send only the fields that changed, so order matters and every notification is kept and replayed in sequence. Which mode applies is built into each connector; there is nothing to configure.

Failed rows wait for you, not the other way around. A failed row does not silently vanish and does not retry on its own. It stays in the queue with its outcome, and the webhook log records what happened, so you can see whether the contact was deleted in the CRM mid-flight, a mapped field was missing, or something else needs a human decision. Fix the cause and re-trigger the change in your CRM, or edit the member directly.

Switching CRMs freezes the other queue, safely. The background worker only processes rows from your active connector. If you switch from one CRM to another, the old connector's waiting rows pause where they are. Switch back and they resume; stay away and rows older than a week are pruned as housekeeping. When rows are waiting under a different connector, the connector chooser tells you how many, so you always know what is still waiting.

Keeping the gate healthy

The gate file is generated, not hand-maintained, and two events call for regeneration:

  • After a plugin update. The gate carries a version stamp, and the settings screen warns you when the installed file is older than the plugin. Regenerating is one click and keeps the file's embedded logic current.
  • After rotating your webhook secret. The secret keys are baked into the gate file at generation time. The sequence is: rotate the key in your settings, regenerate the gate, then let the connector re-register its channels. The screen walks the sequence so it does not depend on memory.

If you have your own code that must run inside the gate, the generated file contains a marked custom region between %%CUSTOM_START%% and %%CUSTOM_END%%. Everything inside those markers survives regeneration verbatim; everything outside is replaced. Custom code there cannot change the reply the gate sends, so the uniform instant response stays intact.

The healthcheck remains the fastest triage tool: it verifies the file exists at the configured URL, reaches the database, and finds the queue table. When webhooks seem to have gone quiet, run it first.

Two kinds of webhooks

Torii receives webhooks in two ways, and the difference is worth having straight:

  • Automation webhooks, the ?operation=... URLs you build into your CRM's automations. These run synchronously: one request in, one action applied, one response back. See Webhooks.
  • Watch notifications, the CRM-generated notifications covered on this page. These arrive at the gate, are acknowledged instantly, and apply in the background.

If your CRM's automation builder can POST a URL, you are using the first kind. The second kind is set up once by connecting your CRM, and after that it runs itself.

Related reading: Connecting a CRM explains the contact and tag sync this machinery feeds, Webhooks covers the automation URLs your CRM's workflows call directly, and Native Webhooks covers updates pushed from tools that are not a CRM.