Integrations
Integrations send registrations to the other tools you use, the moment they happen. Each form has its own: open the form and go to its Integrations tab (next to Reports): it shows each integration and how it stands for that form (active, paused, failing, or not set up). Click one to open its own page. The accounts they use (your Google accounts, MailerLite accounts and Xero organisation) are connected once, in Settings → Integrations (a page for each), and shared by every form. The account owner and Admins set them up.
What integrations do
Each registration is saved in Plantains first. Then a copy goes to each integration you've set up. If the other end is down, Plantains keeps trying, and you can see every attempt. Nothing is lost when another service has a bad day. Each integration can also have rules that decide which registrations it gets.
Webhooks
A webhook sends each registration to a URL you choose, as soon as it's complete. Use it with Zapier, Make, or your own system: anything that can receive a web request. Every request is signed, so the receiving end can check it really came from Plantains.
Adding a webhook
- Open the form, go to its Integrations tab, choose Webhooks and click Add a webhook.
- Paste the URL the other service gives you (it starts with https://).
- Choose what to send: new registrations, changes, refunds, cancellations.
- Copy the signing secret it shows you into the receiving end, if it checks signatures.
- Click Send test: a made-up registration goes out, and you'll see whether it arrived.
https://hooks.zapier.com/hooks/catch/123456/abcdef/
Zapier: new registrations
whsec_8Fq2kZ…ShowNew secretLast: delivered Oct 15, 10:28 AMDeliveriesDelete- The URL registrations go to, and what you named it.
- What it sends.
- Active or Paused: click the switch to pause or resume it. What happens while it's paused isn't sent later.
- Send test: a made-up registration, sent now.
- The signing secret: Show it to copy it, or make a New secret (the old one stops working).
- The latest delivery, and the full log (Deliveries).
What a webhook can send:
- New registration: when a registration is complete, after payment for paid ones.
- Registration changed: answers edited (by the registrant or your team), a transfer, a teammate finishing their part, a waiver signed, a payment.
- Refund and Cancelled.
With Zapier
- In Zapier, make a Zap whose trigger is Webhooks by Zapier → Catch Hook, and copy its URL.
- In Plantains, add a webhook with that URL (the form's Integrations tab → Webhooks), and click Send test.
- Back in Zapier, Test trigger: it finds the test registration. Use its fields (one per answer, like "Rider 1 · Name") in the rest of your Zap.
What's sent
Each request is a POST with a JSON body, sent the moment it happens. The registration is complete in every request: who registered, each participant, what they chose and paid, their tags, and every answer three ways: readable by section (answers), as one flat list for tools like Zapier (fields), and raw by field id (data). Amounts are in cents.
{
"id": "5b7a9c1e-…", // this delivery (also the Plantains-Delivery header)
"type": "registration.created", // also the Plantains-Event header
"created": "2026-10-15T17:28:41.512Z",
"test": false,
"account": { "id": "org_…", "name": "Bike Monkey" },
"form": { "id": "…", "name": "Wente 8 Hour 2027", "slug": "wente-8-hour-2027", "url": "https://plantains.app/forms/wente-8-hour-2027" },
"registration": {
"id": "…", "number": 12, "code": "K7Q2MX", "status": "complete", "archived": false,
"submitted_at": "2026-10-15T17:28:39.004Z", "updated_at": "2026-10-15T17:28:41.200Z",
"email": "ana@example.com",
"people": 2,
"participants": [
{ "section": "Rider 1", "name": "Ana Rivera", "email": "ana@example.com" },
{ "section": "Rider 2", "name": "Ben Rivera", "email": "ben@example.com" }
],
"tags": ["Minor"],
"currency": "usd",
"total_cents": 16000, "paid_cents": 16000, "refunded_cents": 0, "outstanding_cents": 0,
"lines": [
{ "kind": "entry", "label": "Category", "detail": "Duo", "qty": 1, "amount_cents": 16000 }
],
"answers": [
{ "section": "Rider 1", "label": "Name", "text": "Ana Rivera" },
{ "section": "Rider 1", "label": "Birthdate", "text": "5/1/1990" }
],
"fields": {
"Rider 1 · Name": "Ana Rivera",
"Rider 1 · Birthdate": "5/1/1990",
"Rider 2 · Name": "Ben Rivera"
},
"data": { "f_name0001": { "first": "Ana", "last": "Rivera" } }
}
}Each request also has these headers:
Plantains-Event: what happened (registration.created,registration.updated,registration.refunded,registration.cancelled, ortest).Plantains-Delivery: this delivery's id. It's the same on every retry, so you can ignore one you've already handled.Plantains-Signature: the signature (next section).
Checking the signature
Plantains-Signature looks like t=1792087721,v1=5f2c…: the time it was sent (Unix seconds), and an HMAC-SHA256 of <t>.<body> made with your webhook's signing secret. To check it, make the same HMAC from the body exactly as you received it, and compare. Turn away requests older than a few minutes.
import crypto from 'node:crypto'
// rawBody: the request body exactly as received (a string, before parsing it)
function fromPlantains(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(signatureHeader.split(',').map(p => p.split('=')))
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex')
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300 // within 5 minutes
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
}Deliveries and retries
The receiving end should answer with a 2xx status within 10 seconds. If it doesn't, or can't be reached, Plantains tries again later, waiting longer each time, for about a day. Click Deliveries on a webhook to see every one: what was sent, the result, and the receiving end's answer. Use Retry to send a failed one again now. Each registration's History (in Submissions) shows what every integration did with that registration, with Retry there too.
| When | What | Result | |
|---|---|---|---|
| Oct 15, 10:29 AM | Registration changed · #12 · Wente 8 Hour 2027 | FailedThe URL answered 500. | Retry |
| Oct 15, 10:28 AM | New registration · #12 · Wente 8 Hour 2027 | Delivered · 200 | Their answer |
Google Sheets
Keep a form's registrations in a Google Sheet in your own Google Drive: one row per registration, kept up to date. Share it with your team, sort it, add formulas and charts.
- In Settings → Integrations → Google Sheets, click Connect Google. Google asks which Google account, and whether to let Plantains reach "the Google Drive files you use with this app": allow it. You only do this once: the account is there for all your forms.
- Open the form, go to its Integrations tab and choose Google Sheets. Choose a new spreadsheet (Plantains makes it in your Drive) or a spreadsheet you have (choose it in Google's own file picker).
- Name the tab, keep Add the registrations already in ticked to bring in everyone who's registered so far, and click Start sending.
More than one Google account: keep sheets in more than one Google Workspace (yours, a client's)? In Settings → Integrations, click Connect another Google account and choose the other account at Google. When you add a sheet, choose which Google account it's in. Your first account is the Default, chosen first; Make default changes that. Each sheet's card says which account it's in. If a Google Workspace's admin limits which apps can reach it, they may need to allow Plantains first.
Settings → Integrations → Google Sheets
Google account: races@example.comDefault
DisconnectGoogle account: events@clientclub.org
Make defaultDisconnectConnect another Google account
Your form → Integrations → Google Sheets
Wente 8 Hour 2027 registrations
Tab: Registrations · in races@example.com
- In Settings → Integrations: your Google accounts, one per Google Workspace you keep sheets in. The Default is chosen first when you add a sheet.
- The spreadsheet and tab this form goes to.
- Active or Paused: click the switch to pause or resume it. After a pause, Send all again catches the sheet up.
- Open the sheet in Google Sheets.
- Send all again: every registration on the form, rewritten where it is and added where it isn't (after you've tidied the sheet, say).
- The latest update, and every one before it (Updates), with Retry.
How the sheet stays current
- One row per registration, with the same columns as Submissions → Export CSV.
- A new registration adds a row at the bottom as soon as it's complete (after payment, for paid ones).
- A change rewrites that registration's row: an edit, a transfer, a teammate finishing their part, a payment, a refund, a cancellation (its Status says so).
- A new question on the form adds a column at the end.
- The sheet is yours to arrange. Move columns, sort rows, add your own columns (notes, formulas): Plantains finds its columns by their headings and never touches yours.
- Pause or Remove: the switch pauses the sheet and keeps its setup; changes made while it's paused aren't sent later, so click Send all again after you resume. Remove deletes the setup from this form. Either way the spreadsheet stays in your Drive as it is.
| A | B | C | D | E | F | |
|---|---|---|---|---|---|---|
| 1 | Registration ID | Status | Name | Category | Our notes | T-shirt |
| 2 | K7Q2MX | Complete | Ana Rivera | Duo | Paid at expo | M |
| 3 | P3ZD8W | Cancelled | Cy Okafor | Solo | ||
| 4 | M2HX4R | Complete | Dee Smith | Masters | VIP | L |
- Registration ID: how Plantains finds a registration's row again. Keep this column and its heading.
- A column of your own: Plantains leaves it alone, even when it rewrites the row.
- A question added to the form later: a new column at the end.
MailerLite
Add the people who register to a group in your own MailerLite account, for your event emails and newsletters: one group per event, or several.
- In MailerLite, go to Integrations → MailerLite API → Generate new token, and copy the token.
- In Plantains, go to Settings → Integrations → MailerLite. Name the account (e.g. your company), paste the token and click Connect. You only do this once: it's there for all your forms. Plantains keeps it encrypted.
- Open the form's Integrations tab and choose MailerLite. Search for the group and choose it. If it doesn't exist yet, type its name and click Create group "…" in MailerLite.
- Choose who to add:
- Each participant: everyone in the registration (a team's three riders), each as their own subscriber, with the email, name and phone from their own participant section. Participants a registration doesn't include (Participant #2 on a Solo entry) are skipped.
- One person per registration: from the answers you choose, e.g. whoever registered.
- Click Start adding. Tick Also add everyone already registered to bring in the people who registered before.
MailerLite group:Wente - CURRENT
Each participant (Rider 1, Rider 2, Rider 3), with their email, first name, last name, phone
Runs when Add me to the newsletter is checked
Edit rules- The group, who it adds (here, each participant), and what's sent.
- Active or Paused: click the switch to pause or resume it. After a pause, Add everyone catches up.
- Fields: change which answers are sent. Add everyone: everyone already registered, into the group again.
- Its rules: here, only people who ticked the newsletter box.
More than one MailerLite account: building a form for a client who has their own MailerLite? In Settings → Integrations, click Connect another MailerLite account, name it after them and paste their token. When you add a group to their form, choose their account. Your first account is the Default, chosen first when you add a group; Make default changes that. Each group's card says which account it's in.
- New registrations are added when they're complete. Someone already in your MailerLite account is updated and added to the group; they're never taken out of other groups.
- Resubscribe: someone who unsubscribed stays unsubscribed unless you tick Resubscribe people who had unsubscribed. Then registering subscribes them again. "Add everyone" never resubscribes anyone.
- A field in a repeating section (Rider 1, Rider 2…) sends the first entry's answer.
- Pause or Remove: the switch pauses the group and keeps its setup (fields, rules, updates); people who register while it's paused aren't added later, so click Add everyone after you resume. Remove deletes the setup from this form. Either way the group and everyone in it stay in MailerLite as they are.
Xero
Every payment on a form becomes a sales invoice in your Xero organisation, and every refund a credit note against it. Each line goes to the account you chose for its kind, and every line is tagged with the form's event (its Class, say LGF27), so you can run a profit and loss for each event.
- In Settings → Integrations → Xero, click Connect Xero, sign in to Xero and choose your organisation. You only do this once: it's there for all your forms.
- Invoice settings (there too, once, for all your forms). For each kind of line, choose the account it goes to (type a code or name to find it): entry fees, merchandise, fees, donations, discounts, sales tax collected, transfer fees, balance payments, partial refunds and Stripe's fee. Then two tax rates, both 0%: one for income lines (e.g. Tax on Sales (0%)) and one for fee lines (e.g. Tax on Purchases (0%)). Plantains works out the sales tax itself and sends it on its own line, to the account you chose for it, so a rate on the other lines would charge it twice; these only say how Xero files the lines. Then the tracking category that tells events apart (e.g. Event or Class: the category; each event, like LGF 2027, is one of its options, chosen on each form) and whether new invoices are Awaiting Payment or Draft. Nothing is guessed: a form can't send until every one is chosen.
- Open the form's Integrations tab, choose Xero, and choose this form's Class (or Event): search your options, or type a new one (LGF28) and click Add "LGF28" to Xero.
Settings → Integrations → Xero
Xero organisation: Bike Monkey, Inc.
DisconnectInvoice settings (for every form)Every kind of line has an account, and the tax rates are set · events are told apart by Class · new invoices are Awaiting Payment
ChangeYour form → Integrations → Xero
Class:LGF27
| Oct 16, 9:12 AM | Invoice · #212 | Invoice 19-22400 made in Xero: $112.44, Awaiting Payment |
| Oct 18, 2:40 PM | Credit note · #212 | Credit note CN-0412 made in Xero: $20.00, applied to invoice 19-22400 |
- In Settings → Integrations: your Xero organisation, one for all your forms.
- In Settings → Integrations too: the invoice settings every form shares. Change to see or edit them.
- This form's Class, on every line of its invoices and credit notes.
- Every invoice and credit note, with its number in Xero, and Retry for any that didn't go through.
- One invoice per payment, with only what that payment covered: the registration, a later change (an added shirt, a pricier category), a teammate's share, a balance payment or a transfer fee. Invoices are never edited afterwards.
- Itemized: one line per thing bought ("Category: Duo Coed", "T-shirt: M"), sales tax on its own line, and what Stripe kept as a negative line, so the invoice comes to what reaches your bank. A card taken at check-in for a teammate's share (or the rest of the team's) is itemized the same way; a payment of some other amount toward the balance is one line.
- The contact is whoever paid, found by email (or added). The Reference is the form's event and the registration's ID, like Wente 2027 - GK88JW, so Xero's invoice list shows which event each is for, and searching the ID in Xero shows everything for that registration. The date is the payment's, and numbers follow Xero's own sequence.
- A refund becomes a credit note: a full refund reverses the invoice's lines (Stripe's fee isn't given back, so it stays); a partial one is one line on your partial-refunds account. With Awaiting Payment, it's applied to the invoice's amount due.
- Only real (live) payments are sent. To try it out, connect Xero's demo company and tick Send test-mode payments too; untick it before connecting your real organisation. The demo company comes with its own sample accounts and a sample tracking category (Region): add accounts and a Class category like your real ones first, then reload from Xero in Settings → Integrations. Payments recorded by hand (cash, check) and payments made before you set Xero up aren't sent.
- Pause or Remove: payments made while it's paused aren't sent later. What's already in Xero is never changed.
Rules: when an integration runs
With no rules, an integration gets every registration. Rules narrow that down, the same way show/hide rules work in the builder: all or any of a list of conditions. For example, send to your newsletter tool only when the payment went through and "Add me to the newsletter" is ticked, or keep a sheet of Duo teams only.
A rule can check:
- An answer: anything the builder's show/hide rules can check (Category is Duo, age on event day is less than 18, a box is ticked…).
- Registration: Complete, Waiting for teammates, or Cancelled.
- Payment: Paid in full, Balance owed, or Nothing to pay.
- Payment mode: Live, Test, or Paid outside Plantains (marked as paid by hand).
- How an integration before it went for the same registration: Succeeded, Failed or Didn't run, or what a webhook answered. This one then waits for that one: see Run order.
In the form's Integrations tab, click Add rules on its row in the Run order, or open the integration and click Add rules on a sheet, MailerLite group, Xero or webhook. It then says when it runs.
Run when all of these are true:
- All or any: whether every rule must be true, or one is enough.
- A rule on an answer, like in the builder.
- A rule on the registration itself: its status, payment or payment mode.
A registration the rules stop is shown in the log as Skipped, with the reason, so you can tell why something didn't arrive:
| Oct 15, 3:07 PM | New registration · #42 | SkippedRule not met: Add me to the newsletter is checked |
| Oct 15, 3:02 PM | New registration · #41 | Delivered · 200 |
- Rules are checked when something happens, against the registration as it is then. A skipped one isn't sent later.
- On a Google Sheet, Send all again only writes the registrations the rules allow. A registration that stops meeting them (say it moves from Duo to Solo) is no longer updated, and its row stays as it was.
- If a rule needs attention (its field was removed from the form), the integration sends nothing until you fix or remove the rule. The card says so in red.
- A webhook that sends more than one form (set up before integrations moved into forms) can check the registration, but not answers or other integrations.
Run order: one waiting for another
Everything a form's integrations do (each sheet, each MailerLite group, Xero, each webhook) is listed under the tiles in its Integrations tab, in its Run order. When something happens to a registration, they all run at the same time. As long as none of them waits for another, the order doesn't matter, and the list says so: All at the same time.
Sometimes one should only run if another worked, or only if it didn't. For example: add people to MailerLite only once their Xero invoice went through, or call your own system when Xero failed. To set that up:
- In the Run order, drag the one that should wait below the one it depends on (by its handle, on the left). One can only wait for those above it.
- Click Add rules (or Edit rules) on its row: its rules open right there. Choose the one above it under Integrations before this one, and Save rules.
- Choose what it needs: is Succeeded, is Failed, is Didn't run (or is not). For a webhook, you can also check what it answered with: a status like 201, or a range like 4xx.
The list then numbers them, and shows which run right away and which wait for which:
- Drag by the handle. A spot where it can't go (above one it waits for) turns red, and it's put back.
- Runs as soon as something happens to a registration.
- Waits for #1, and runs when its rule is met.
- Opens its rules in the row, to add or change them without leaving the list.
What each outcome means:
- Succeeded: it was sent (the row written, the person added, the invoice made, the webhook answered 2xx).
- Failed: it couldn't be sent. Plantains keeps trying for about a day first, so a rule on Failed can take that long to be decided, unless trying again can't help (Xero's settings unfinished, access lost): then it's right away.
- Didn't run: it had nothing to do. It was paused, its own rules stopped it, or it doesn't hear about this kind of thing. Xero only hears about payments and refunds, so for a free registration, or an edit, Xero didn't run.
- A webhook's answer: only met when the webhook answered. Plantains tries again after any answer outside 200–299, so to steer what happens next straight away, have your system answer with different 2xx statuses (200, 201, 202…).
- "The same registration, at the same moment" means everything one thing sets off: a payment going through (the new registration and its Xero invoice), a refund (its credit note, and the cancellation if you cancel with it), an edit.
- While it waits, its log shows Waiting, and what for. If the rule isn't met, it's Skipped with the reason, like "Rule not met: Xero: Wente 8 Hour 2027 succeeded (it failed)".
- Retrying the earlier one (its Retry button) checks the ones it stopped again: if it now succeeds, the ones waiting for that run. Ones that already ran aren't undone.
- Add everyone, and Send all again, use how the earlier one last went for each registration.
- Removing one that others wait for leaves their rule needing attention (in red), so they send nothing until you change it.