Zapier integration
Paid add-on. A New submission trigger on the Zapier marketplace: you pick a form from a dropdown, and every answer goes to the Zap. Six thousand services connect to it without writing a line.
This document describes what the module guarantees and what it refuses. The user guide gives the short version, under “Connecting a form to Zapier”.
What Zapier adds to the webhook
Technically nothing: both send signed JSON to an address. The generic webhook remains the fallback, and it remains the right tool for an in-house service.
What Zapier brings has to do with who uses it. You pick a form from a dropdown rather than pasting an address, you see the available fields before having received anything, and you connect Google Sheets, Notion, Slack or a CRM without configuring anything else. The webhook requires you to know what an HTTP hook is; this does not.
The subscription comes from Zapier, the rest comes from the site
That split governs the whole module.
Zapier decides where to deliver. It creates a subscription when a Zap is switched on, and removes it when it is switched off. Those rows are therefore never written by an administrator, and that is why they live in a table rather than in the form’s JSON settings: that document would have been written by two hands — the builder screen and an HTTP request — with one of the two lost at the first crossing.
The site decides whether and what. The form’s switch, the send condition and the field list live in the builder.
The split is not arbitrary. A setting that lives in Zapier can be changed by whoever has access to the Zapier account; the data, meanwhile, belongs to the site. What restricts it must be there too.
Subscribing twice subscribes once
Zapier re-sends a subscription as soon as it is not certain the first one
succeeded — a network blip at the wrong moment is enough. The unique key
(form_id, fingerprint of the address) makes the duplicate impossible, and the
route returns the subscription already in place rather than an error: from
Zapier’s point of view the request is satisfied, and it is.
Without that, one Zap would have received every submission twice.
The sample is built, never taken
That is the module’s central decision.
While you build a Zap, Zapier shows a sample item so that you can map the columns. The obvious way to supply it is to return the most recent submission. It is also the worst: that sample is kept in the Zap’s configuration, visible to everyone who has access to it, exported with it, and it stays there when the submission is erased from the site.
A quote request containing a name, a phone number and a message would thus be copied into a third-party service, outside any GDPR purge, because somebody clicked “Test trigger”.
The sample is therefore built from the field definitions: their id, their label, and a demonstration value chosen according to their type. It has exactly the shape of a real delivery — which is all you need to map columns — and it contains nobody’s data.
It follows the whitelist: a field that will not go out does not appear in the sample. Otherwise you would map a column that stayed empty forever, and look for the fault on the wrong side.
The whitelist
Empty, it lets every non-sensitive field out — exactly what the generic webhook does, towards an address the site also chose. Imposing a list would make a freshly switched-on Zapier an integration that sends nothing, and that you would believe broken.
It exists for the case where the Zap leads somewhere other than home: a shared spreadsheet, a contractor. Ticking three fields is then a deliberate act, and that is the right moment to perform it.
Four types never go out, whatever you tick: password, payment, file and signature. The first two because they are set aside upstream by the value formatting, as for emails and exports. The other two because an upload address is permanent access: pasting it into a Zap would copy it into a third-party service, outside any expiry.
A field removed from the form leaves the list by itself. Without that filtering, a list that had become entirely obsolete would behave like a non-empty list, and the Zap would stop receiving anything with nothing to explain it.
The send condition
The same structure as a field’s display logic and as a notification’s conditional send, evaluated by the same code. Three condition mechanisms for three uses would have given three behaviours to explain, and two to fix the day the first turns out to be faulty.
An absent condition lets everything through: that is the common case, and requiring a rule in order to deliver would make a freshly switched-on Zapier a mute integration.
Delivery
Asynchronous. Calling Zapier during the submission would make the visitor wait for a network round trip they did not ask for. The submission is already recorded at that point: nothing justifies tying its fate to a third party’s.
One task per subscription. A form can feed three Zaps. Delivering them together would make the last two depend on the first one’s fate: a broken Zap would delay the others by its timeout, and a retry would replay all three — two of which had succeeded.
The task carries only ids. The payload is recomposed at the moment of sending. Copying submission values into the scheduled-tasks table would make it a second copy of personal data, which would outlive the erasure of the first.
Everything is re-checked at send time. The setting may have been switched off, the field removed from the list, the submission trashed between the scheduling and the scheduler’s pass — a few minutes, sometimes half an hour on a third attempt. What was true at submission time is not necessarily still true, and a delivery goes to a third party: we re-read.
Deduplication
Every delivery carries an id, slf-<subscription>-<submission>. Zapier
deduplicates on that key: two deliveries carrying the same id trigger only one
Zap.
It depends neither on the time nor on the attempt number. A third attempt therefore carries the same id as the first, which is the whole point: a retry after a network failure creates no second invoice, no second email and no second spreadsheet row.
Retrying
The same rule as the webhook: three attempts, spaced a minute, five then thirty. A network failure, an outage or a rate limit are replayed; a request refused for its contents is not — it would be no more accepted on the third attempt.
A switched-off Zap unsubscribes itself
Zapier answers 410 Gone at the address of a stopped Zap. That is the convention
of its hook protocol, and it is useful: without it, a Zap switched off on day one
would keep receiving deliveries until somebody thought to clean the table. The
subscription is therefore removed, and the retry cancelled.
The signature
A timestamp and a HMAC-SHA256 of “timestamp.body”, in an X-SolisForms-Signature
header of the form t=…,v1=…. That is this plugin’s webhook scheme and Stripe’s:
a recipient already equipped for one has nothing new to write for the other. The
timestamp bounds the replay window.
The secret is not stored. It is derived from the site salt and the subscription’s row number, and recomputed on every send. Whoever reads the table therefore cannot forge a signed delivery, and rotating the salt invalidates every signature in circulation at a stroke.
That is a difference from the project’s other secrets. A resume link or a portal’s private link are verified: we compare what we receive against a fingerprint, and the database never needs the secret. This one is produced: you need the key to sign. Storing it in clear would have been the obvious solution, and a database that was read would have given the means to forge deliveries to every Zap on the site.
We verify nothing on our side: the signature is emitted, and it is Zapier that verifies it if it chooses to. No constant-time comparison therefore takes place here, and announcing one would describe a check that does not exist.
Rights
Authentication is WordPress’s, through an application password — in the core since 5.6, specific to one application, revocable from the user’s profile without touching the account’s password.
The plan recommended OAuth. WordPress ships no authorisation server: offering it would mean writing one, that is, one more authentication surface to maintain, on a plugin that already has enough. What OAuth would have brought is consent by scope — “this Zap may read this form, and only it”. Here the scope is the account’s: that is written down rather than left unsaid, and it is the reason we recommend a dedicated automation account.
Every route requires two checks: the capability to view submissions, and access to the form targeted. The first alone would let a manager of their own forms subscribe a Zap to somebody else’s, by changing a number in the request.
Unsubscribing additionally requires being the subscription’s owner. Without that check, a guessed row number would switch off somebody else’s Zap — and nothing would signal it, since a switched-off Zap says nothing.
A subscription is worth the right of whoever created it: if the account is deleted, the row goes with it. WordPress reassigns ids, and an orphaned subscription would deliver in the name of the next person to register.
The rate limit
Thirty subscriptions per hour per account. It applies to subscribing, which is called from outside and writes a row — not to delivery, which starts from here. Without a bound, a badly configured loop at a third party would fill the table.
The log
Every delivery is recorded in the submission’s delivery log, on the same footing as an email or a webhook, with its HTTP code, the attempt number and the answer received. It is read on the submission’s detail screen.
The Zapier client
It lives in and is not shipped with
the plugin: Zapier hosts its marketplace’s clients. Three files —
authentication, the trigger, the form dropdown — and no business logic, which
sits on the WordPress side, where the data lives.zapier-app/
The routes
GET /wp-json/solis-forms/v1/zapier/forms
POST /wp-json/solis-forms/v1/zapier/subscriptions { form_id, target_url }
DELETE /wp-json/solis-forms/v1/zapier/subscriptions/<id>
GET /wp-json/solis-forms/v1/zapier/sample?form_id=<id>
What V1 does not do
- No Zapier action: nothing comes to modify an entry or create one from Zapier. The trigger goes one way.
- No entry search, no file transfer.
- No event other than creation: payment and moderation will come as separate triggers, not as variants of this one.
- No plain URL-receiving route: the generic webhook already covers it.
