GoTeamUp → Suite: Field Sync (n8n engine)
Plain-English summary
When a customer's details change in GoTeamUp (the gym CRM), this workflow notices, works out what actually changed, and pushes only the real changes into the DaySmart Business App marketing suite. It is the one-way engine for keeping the marketing suite in step with GoTeamUp. It is deliberately generic: it watches a configurable list of fields rather than being hard-wired to any single one, so new fields can be added without building a new workflow.
How it works (step by step)
This is the n8n workflow id wE8C4ipALesZpken, canonical name GoTeamUp → Suite: Field Sync. It runs on the self-hosted n8n at https://automation.mvmtribe.com (Docker container mvt-n8n, with Postgres in container mvt-n8n-db). It has 7 nodes:
- GoTeamUp Webhook: a production webhook on path
goteamup-customer-updated. GoTeamUp calls it whenever a watched customer event fires. - Extract Customer ID: parses the incoming GoTeamUp Feed Item payload, shaped
{feed_items:[{type, resources:{customer}}]}, and pulls out the customer id. It also tolerates a bare{id}payload, so manual or test calls still work. - GET Customer: calls
GET https://goteamup.com/api/v2/customers/{id}?expand=field_valuesto fetch the full current customer record including custom field values. Auth is two headers:Authorization: Token <M2M>andTeamUp-Provider-ID: 4665235. - Build Changes: maps the watched GoTeamUp field ids to a normalised shape
{external_id: "teamup-" + id, field, value}. Formarketing_preferenceit translates the raw GoTeamUp choice string into a clean value:"Yes, I'd like to receive occasional marketing messages"becomesyes, and"No, do not send me any marketing messages"becomesno. - Upsert + Detect Change: a Postgres step. A CTE reads the previously stored value for this customer and field, upserts the new value, and RETURNS
changed = old IS DISTINCT FROM new. This is the change-detection heart: it is how we avoid pushing no-op updates downstream. - If Changed: passes through only when
changedis true. - POST to Suite: posts the JSON body
{external_id, field, value}to the suite inbound webhook, which hands off to the suite applier (see linked doc).
Change-detection state
The state lives in the Postgres n8n database on mvt-n8n-db, schema mirror:
mirror.field_mirror_state(teamup_id, field_key, value, gt_raw_value, last_seen_at, last_pushed_at): the last known value per customer per field, plus the raw GoTeamUp value and timestamps.mirror.watched_fields(field_key, gt_field_id, active): the list of fields the engine watches. Currently one row:marketing_preference / 227405 / true.
Dependencies
- Credentials (names only, see STANDARD.md section 6):
n8n cred odUGMC6NZNAKlkQh= GoTeamUp M2M token, used by GET Customer.n8n cred TAZ5cYldQx0U4vAw= Mirror Postgres, used by Upsert + Detect Change.
- Data stores:
mirror.field_mirror_stateandmirror.watched_fields(both in then8nPostgres database, schemamirror). - Downstream: calls the suite applier
GoTeamUp → Suite: Field Sync(businessapp), documented ingoteamup-suite-field-sync-suite.md. - External API: GoTeamUp API v2 (
goteamup.com/api/v2). - Upstream wiring (GoTeamUp webhook destination): GoTeamUp webhook destination id
11014, active, its url pointed at our n8n production webhook,feed_item_types = ["customer.updated"],application = 307. Managed through the GoTeamUp APIGET/POST /webhook_destinations. GoTeamUp retries 5 times (at 1, 3, 10, and 30 minute intervals) if it does not get a 2xx within 10 seconds, and it sends a dedup headerTEAMUP-WEBHOOK-ID.
Failure modes and gotchas
Publish and activate gotcha (the big one)
This n8n build (v2.44.x) has a review and publication pipeline. Two traps follow from that:
n8n import:workflowimports the workflow but leaves it DEACTIVATED. Settingactive = truein the database directly does NOT register the production webhook, so GoTeamUp calls will hit a "not registered" response.- The correct sequence to publish and register the webhook is:
docker exec mvt-n8n n8n update:workflow --id=wE8C4ipALesZpken --active=true(this aliases topublish:workflow),- then
docker restart mvt-n8n.
- Verify by curling the production webhook: a healthy result is HTTP 200, not a "webhook not registered" message.
File permissions on import
docker cp writes files into the container as root. Before n8n import:workflow can read them, fix the permissions: docker exec -u root mvt-n8n chmod 644 <file>.
Idempotency and retries
GoTeamUp retries on non-2xx and sends a TEAMUP-WEBHOOK-ID dedup header. The Upsert + Detect Change step is the safety net here: because a retry of an already-applied change will find old IS DISTINCT FROM new is false, it will not re-push downstream. Return 2xx quickly (within the 10 second window) to avoid unnecessary retries.
No-op suppression
Only true changes pass the If Changed gate. If a customer record is saved in GoTeamUp without the watched field actually changing, nothing is sent to the suite. This is intended.
Change log
- 2026-10-21: Documented. Workflow LIVE and verified end-to-end. One watched field:
marketing_preference(GoTeamUp field id 227405).