Native Webhooks

Most webhook operations speak CRM. The payload is a contact export, and a connector has to decode it, match fields, and translate tags before anything lands. The update_member_data operation skips all of that: the payload already speaks Torii. You name the member by WordPress user ID or email, name fields by their local slug or ID, and name tags by their tag IDs. Torii validates every key, then applies the whole update in one pass and tells you what it did.

Because nothing needs translating, no connector has to be active. Anything that can POST JSON can drive it: Zapier, n8n, Make, a shell script. If you do have a CRM connected, fields and tags that map to the CRM still sync up after the update lands, the same as an edit made by hand.

The URL

https://YOURSITE.com/?operation=update_member_data&membauth=YOUR_AUTH_KEY

Replace YOURSITE with your domain and YOUR_AUTH_KEY with one of your webhook secret keys from the Torii settings. The request is a POST with a JSON body.

The key can also travel in the x-torii-auth header, and the operation in an x-torii-operation header. The query string version needs no custom headers, which is why most tools use it.

A complete call:

curl -X POST 'https://YOURSITE.com/?operation=update_member_data&membauth=YOUR_AUTH_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"email":"member@example.com","fields":{"newsletter_optin":"yes"},"add_tags":[12]}'

The payload

{
  "user_id": 123,
  "email": "member@example.com",
  "fields":     { "newsletter_optin": "yes", "42": "any value" },
  "add_tags":    [ 12, 34 ],
  "remove_tags": [ 7 ]
}
SectionRules
`user_id`Optional. The WordPress user ID. When present it is authoritative: if no user has that ID, the delivery is declined rather than falling back to the email, which might belong to someone else.
`email`Optional. Looked up as the WordPress account email. Give at least one of `user_id` or `email`; when both are present, `user_id` wins.
`fields`Optional. Keys are local field slugs or numeric field IDs; both identify exactly one field. Values are stored as text, and an empty string clears the value.
`add_tags`Optional. Local tag IDs only, integers or digit strings.
`remove_tags`Optional. Same rules as `add_tags`.

You can add and remove tags in the same delivery, and set any number of fields. A delivery carrying none of the three sections is declined.

Tag names are rejected on purpose. A name can be renamed or duplicated; an ID stays pinned to one tag. An automation that grabbed the wrong tag silently is worse than one that got a clear error back.

One bad key declines the whole delivery

Every field key and every tag ID is resolved and existence-checked before anything is written. One unknown key and the entire delivery comes back declined with every offender named. Nothing is half-applied, and nothing half-syncs to your CRM. Callers are automations, and silent skips hide typos; a loud 400 fixes them.

What you sentWhat comes back
No fields, no tags400, `No fields or tags specified.`
An unknown field slug or ID, or a bad tag ID400, with every offender listed
A tag name instead of an ID400, naming the entry
A `user_id` or `email` no member has400, `No user found for update operation.`
Unparseable JSON, or no identity in the body400, `Unreadable webhook data.`
A member with admin capabilities403, admins are protected from webhook updates
A bad or missing key403, before the payload is even read

The reply

Responses are JSON. Success looks like:

{ "success": true, "data": "Completed." }

A 2xx means the update landed. A 4xx is a decline: the reason sits in data, as the table above shows. A 5xx means something failed on the server. Every delivery, successful or not, is recorded in the webhook log with its outcome, so you can check what happened without guessing.

What syncs up

The update goes through the same path as an edit made in the WordPress admin. A field or tag that maps to your CRM syncs up through the connector, exactly as if you had edited the member yourself. A local-only field or tag has no CRM counterpart to translate into, so nothing is sent: the mapping is the filter, and there is nothing extra to configure. When your CRM echoes the change back, the existing update handling recognizes its own push and the loop ends there.

With the webhook gate enabled

The URL above runs synchronously: one request in, one update applied, one response back. If you have generated the webhook gate file and post to it instead, deliveries are acknowledged instantly and applied in the background, with the operation and key preserved through the queue. The gate and its queue are covered in Advanced Webhook Handling.

For developers

The handler is torii_webhooks_class::elf_update_member_data(). Its decoder, elf_decode_direct(), runs on the wpal/torii/webhooks/decode filter at priority 5 and claims the payload only when the routed operation is update_member_data, so no connector decoder can interpret it. Writes go through torii_field_values_class::elf_set_field() and torii_tag_assignments::elf_add_tags()/elf_remove_tags(), which fire wpal/torii/member/fields/updated and wpal/torii/member/tags/added/removed. Listeners on those hooks, including the n8n push, credits, and profile builder, see the change the same as any local edit.

Guides by tool

The walkthroughs below wire the same URL and payload into each automation builder:

Related reading: Webhooks covers the CRM-shaped operations (create_member, update_member, and friends) whose payloads your connector decodes.