
CUSTOM WORDPRESS INTEGRATIONS
Keeping a marketing CRM honest against a government reporting API
A scheduled bridge that keeps a marketing CRM honest against a government reporting API, without ever touching the one field that makes matching possible.
5
PHP classes
6
match variants per record
0
custom tables
0
public endpoints
| Client | A Dutch programme office running a shared-data ecosystem for logistics and mobility partners (not named) |
|---|---|
| Challenge | Partner onboarding status lived in a governance reporting API while member communication ran from FluentCRM. The two never agreed, and staff copied status across by hand. |
| Delivery | A WordPress sync plugin end to end, plus the CRM field and tag architecture, automations, email deliverability, platform configuration, go-live and handover. |
| Stack | WordPress, PHP, FluentCRM, JavaScript |
| Timeline | January to February 2026, v0.5.0 to v1.0.0 |
The challenge
Every partner in the ecosystem is identified by an EORI number, the EU customs identifier. The programme’s governance API reports, per EORI, where each partner stands in onboarding: registered in the participant registry, ready to transact, offer published, and how long each step took. Member communication lived somewhere else entirely — FluentCRM held the contact list, the tags, and the email automations that nudge partners through onboarding. Someone read status out of one system and typed it into the other. It happened late, it happened inconsistently, and automations built on stale data sent the wrong nudge to the wrong partner.
The join key made it hard. The EORI number was the only field both systems shared, and it had been hand-entered for months: some contacts carried the EU.EORI. prefix, some did not, some were lower-case — while the API could return records as a plain list or as a map keyed by identifier. A naive string match would have silently skipped most of the list and reported success. Worse, the key lived in the same custom-field table as the fields being written, so one careless mapping could overwrite the EORI itself and break every future match for that contact with no error.
And FluentCRM’s own custom-field update method is quiet: it writes the value but does not raise the contact-updated event that automations listen to. Writing the data correctly was not enough — the CRM had to notice.
What ScalarWP built
ScalarWP built a small WordPress plugin around one scheduled job — hourly, twice daily, daily, weekly, or off. Each run fetches the reporting API with a bearer token, treats transport errors, bad responses and malformed JSON as distinct logged failures, normalises whichever payload shape arrives, and queries FluentCRM for every contact with an EORI — optionally scoped to selected tags through a single pivot-table subquery, so ten thousand contacts scoped to two hundred active partners cost one query.
Matching tolerates the data as it actually exists. Every API record is pre-indexed under six identifier variants — original, upper, lower, prefixed, unprefixed and case-shifted — so the common path is one hash lookup, with a linear prefix-stripped fallback only when all six miss. Cleaning months of hand-typed identifiers first would have blocked launch on the messiest part of the client’s data; normalising the key instead let the sync go live against the data they had.
The join key itself is structurally read-only: the mapping validator refuses it, the sync loop skips it, and the final write set drops it unconditionally. Three redundant guards, because the failure they prevent — a silently corrupted identifier — is invisible until the next run reports the contact as unmatched. And because FluentCRM’s field writer is silent, the sync snapshots each contact, writes, re-reads, compares, and only then raises FluentCRM’s own change event, tagged with the sync as its source. Automations fire exactly once per real change; a run that changes nothing raises nothing.
Administrators run all of it from one screen: API credentials and schedule, field mappings populated live from FluentCRM, a searchable tag filter, a run-now button reporting synced, unmatched and error counts inline, and a run history capped at twenty entries — split into its own option with a one-time migration after the first release’s unbounded log started growing inside the settings. Around the plugin, ScalarWP built the destination first: the CRM fields, tag structure, automation funnels and email templates, then domain authentication (SPF, DKIM, DMARC) so the automated mail actually arrives, then go-live and training.
The outcome
- Partner status now updates itself on the chosen schedule, replacing the manual copying step between the two systems.
- 1,731 lines across PHP, JS and CSS in five classes — v0.5.0 to v1.0.0, January to February 2026.
- Zero custom tables and zero public REST routes: settings and a capped history live in four WordPress options.
- One scheduled event, four authenticated AJAX actions, and one FluentCRM event raised — the platform’s own, so the client’s automations are ordinary FluentCRM automations.
- Field mappings, tag scoping and schedule are all configurable without a developer; the client team operates the sync today.
What ScalarWP delivered
- A resumable scheduled sync engine with six-variant identifier matching and per-field change detection.
- A structurally read-only join key, guarded at three layers.
- One admin screen: credentials, schedule, live field mapping, tag scoping, run-now, and a capped run history.
- The FluentCRM field, tag and automation architecture the sync writes into, with email templates and contact import.
- Email deliverability (SPF, DKIM, DMARC), platform configuration, go-live, training and handover.
Why it mattered
A bridge like this earns trust by what it refuses to do: it never writes the key it matches on, never invents its own events, and never keeps more history than the question it answers. Raising FluentCRM’s own event with the platform’s own signature means nothing downstream knows a sync exists — the programme office runs ordinary automations on data that is finally current, and edits mappings, tags and cadence without calling a developer.
Two systems that never agree?
Tell us where the copying happens by hand. We will scope the smallest bridge that keeps both sides honest — and your team runs it without us.

