I like Zapier. It is genuinely useful for proving an idea quickly. But once the church Spiritual Gifts workflow got real, Zapier started feeling less like automation and more like trying to remodel a kitchen with a pocket knife. Useful tool. Wrong scale.
The original problem sounded simple: someone submits the Spiritual Gifts assessment, we score the gifts, then update their Planning Center profile. But the real workflow is not one tiny action. It is parsing a big form, normalizing names and dates, calculating multiple gift scores, finding the right Planning Center person, loading the right custom-field tab, updating fourteen score fields, writing metadata, and handling errors safely.
That is where the five-step style Zapier workflow started falling apart. So we built our own box.
Why Zapier broke down
Zapier was perfect for the first prototype. It let us prove the shape of the workflow fast:
- Jotform receives a Spiritual Gifts submission.
- Zapier catches the new submission.
- A Code by Zapier step calculates or maps data.
- Planning Center gets updated.
That was enough to validate the idea. But production workflows are where the gremlins come out of the vents.
Step pressure
Every little thing wants to become another Zap step: lookup person, lookup field definitions, update one field, update another field, notify on failure, log a result, branch for missing people. The workflow becomes a Jenga tower.
Fields to update
The Spiritual Gifts assessment has fourteen score categories, plus metadata like Source, Date Updated, and optional Strengths. Updating them as separate Zapier actions is painfully fragile.
The deeper issue was not just cost or task limits. It was control. We needed proper code, not a chain of tiny UI boxes. We needed one place where the workflow could parse weird form payloads, handle edge cases, call Planning Center as many times as needed, and produce logs we could actually use.
| Area | Zapier prototype | Webhook box |
|---|---|---|
| Form payloads | Works when the trigger maps fields cleanly. | Can parse multipart Jotform payloads directly and tolerate odd field names. |
| Planning Center updates | Easy for one or two fields, ugly for fourteen plus metadata. | One code path searches the person, loads fields, and updates all relevant values. |
| Limits | Constrained by plan, tasks, steps, and Zap action design. | Limited by our server resources and the actual third-party API limits. |
| Debugging | Click through Zap runs and hope the useful payload is visible. | Use journal logs, structured errors, dry-run endpoints, and targeted test requests. |
| Security | Secrets live inside Zapier input fields. | Secrets live in the server environment; public endpoint is token-protected. |
| Future changes | Add more steps, branches, or another Zap. | Edit normal code, validate it, restart the service. |
What we built instead
We built a dedicated Life Community Church webhook server on Proxmox. The public domain is home.buckeyemanor.com/webhooks, and the app lives on the webhook server at /opt/church-webhook.
The Spiritual Gifts production path is now:
Jotform submission
→ https://home.buckeyemanor.com/webhooks/webhook/spiritual-gifts?token=...
→ /opt/church-webhook/SpiritualGifts/spiritual-gifts-api.js
→ Planning Center People API
→ Spiritual Gifts tab id 256800
Instead of Zapier orchestrating a bunch of little actions, the church-owned webhook app owns the whole transaction. That lets us make the workflow understandable as code:
- Accept the incoming webhook from Jotform.
- Authenticate it with a shared token.
- Parse JSON, URL-encoded, or multipart form payloads.
- Normalize email, name, submission date, and score fields.
- Search Planning Center People by email.
- Load custom field definitions and prefer the Spiritual Gifts tab.
- Update all gift score fields and metadata.
- Return a useful JSON result.
- Write logs that make support possible.
Architecture diagram
Request flow in detail
1. Jotform submits directly to our webhook
Instead of using Zapier as the middleman, Jotform’s Webhooks integration posts straight to:
https://home.buckeyemanor.com/webhooks/webhook/spiritual-gifts?token=...
The token is important. It means the endpoint can be public without accepting random internet noise as a valid church form submission.
2. Express accepts the payload
The Node/Express app receives the request. This mattered more than expected because Jotform sends webhook data as multipart/form-data in real life. A normal JSON parser does not see that body. Early testing showed empty body keys even though the submission was arriving. So we added multipart parsing with multer.
3. The Spiritual Gifts module normalizes the data
Form tools love to send data in whatever shape they feel like sending that day. The webhook code normalizes the useful pieces:
- Email address for Planning Center lookup.
- Name when available, while ignoring Jotform metadata fields like usernames or form titles.
- Submission date, with safe fallback if Jotform sends a weird date object.
- The fourteen Spiritual Gifts scores.
The endpoint also supports a dry-run path, which is one of the best things we gained by leaving Zapier. We can test parsing without writing to Planning Center.
POST /webhook/spiritual-gifts/dry-run?token=...
4. The service updates Planning Center
Planning Center People is updated through the API using credentials stored in the server environment. The code searches by email, loads field definitions, prefers the Spiritual Gifts tab, then updates all relevant fields in one controlled workflow.
That includes the score fields:
AdministrationDiscernmentEncouragementEvangelismFaithGivingServiceKnowledgeLeadershipMercyShepherdingProphecyTeachingWisdom
It can also write metadata like Date Updated, Source, and optional Strengths when the field exists.
Planning Center update logic
The biggest technical detail is that Planning Center custom fields are not just simple key/value properties. The code has to know which field definitions exist, which tab they belong to, and which field names match our gifts.
We specifically prefer the Spiritual Gifts tab with id 256800. That prevents a duplicate field name elsewhere in Planning Center from stealing the update. If a field is missing, the code should make the problem obvious instead of silently skipping it.
Search person by submitted email
→ load Planning Center field definitions
→ filter/prefer Spiritual Gifts tab 256800
→ match gift field labels
→ upsert score field data
→ write Date Updated / Source / Strengths
→ log success or structured skip reason
That kind of logic is possible in Zapier, but it is awkward. In normal code, it is just a function. And functions are much easier to test, read, reuse, and fix at midnight when something inevitably does something spicy.
What “unlimited” really means
Calling this “unlimited” does not mean we can ignore every limit in the universe. Planning Center still has API limits. The server still has CPU, memory, and disk. Jotform still has its own behavior. What changed is that we are no longer boxed into an automation plan’s step count or task model.
No artificial step ceiling
The workflow can make the calls it needs to make. If it takes ten internal operations to handle one submission correctly, that is fine.
No per-field Zap action pileup
Fourteen gift fields plus metadata can be handled in one service flow instead of one UI action per update.
Better retries and diagnostics
We can log exact skip reasons like missing email, no matching person, missing field definition, or Planning Center error.
Reusable platform
The same webhook box can host Spiritual Gifts, HVAC, Caddy-backed support pages, and future church integrations.
Operations and support
The webhook app is normal Linux infrastructure. That is the whole point. When something breaks, we do not have to click through a maze of Zap history screens. We can inspect the service.
cd /opt/church-webhook
node --check app.js
node --check SpiritualGifts/spiritual-gifts-api.js
systemctl status church-webhook --no-pager
journalctl -u church-webhook --since "10 minutes ago" --no-pager
Useful live checks:
curl -s https://home.buckeyemanor.com/webhooks/spiritual-gifts/health
journalctl -u church-webhook -f | grep "Spiritual Gifts webhook"
The deployment pattern is intentionally boring:
- Back up the current file before editing.
- Patch the app or module.
- Run
node --check. - Restart
church-webhook. - Watch logs while submitting a test form.
That is not glamorous. It is better than glamorous. It is supportable.