# Method selection

Test the methods in this order and take the first one that passes every clause of the contract: native connector, iPaaS, event pipeline, warehouse plus reverse ETL, custom code, file exchange, MCP access. Record in `method_rationale` the clause each simpler method failed. A richer tool never compensates for an unresolved identity, ownership or removal rule; settle those first ([contract-rules.md](contract-rules.md)).

The thresholds below are practitioner defaults. Tune them per team and record any change, with its reason, in the spec.

## MP01 Native connector

Passes only if every one of these holds:

- Covers the exact objects, including custom objects and the associations the decision needs. "Integrates with X" is not a specification.
- Sets direction per field, not only per object.
- Sets blank handling per field (absent, empty and explicit clear behave as the contract says).
- Matches on a key the team controls: a stable ID in a unique field. Matching on name or domain fails.
- Shows record-level errors somewhere an operations owner reads.
- Supports the removal behaviour the contract needs (unsubscribe, exit, delete, merge).
- The connector's sync interval plus observed backlog drain fits the freshness target (interval from the vendor docs, backlog from the sync status page).
- A global conflict setting ("this system wins") on a two-way sync fails any decision-driving field owned by the other system.

Where to look:

- HubSpot data sync apps: the sync direction, the conflict preference and the blank-value behaviour in the app's settings, and the field mappings with their per-type constraints. Sync filters govern initial inclusion; records already synced can keep syncing after they stop matching the filter.
- HubSpot Salesforce connector: the inclusion list, each property mapping's sync rule, and the connector's Salesforce API use. HubSpot's installation guide says a single contact sync can take up to four API calls per record, and a segment resync needs at least three available calls per member (knowledge base, checked 2026-11-02). The connected Salesforce user's permissions decide what the sync can read and write.
- Any connector: export or screenshot the mapping screen, then read field history on the writer sample in [contract-rules.md](contract-rules.md) to see what the connector actually writes.

Questions for the vendor, one per clause:

- Which objects and associations sync, in which direction, per field?
- What happens when the source value is blank, null or removed?
- Which field is the match key, and what happens on two matches or none?
- Where do record-level failures appear, and for how long?
- What does the connector do when a record leaves the source population or is merged or deleted?
- How often does it sync, and how long does a backlog take to drain?
- How many API calls does one record cost on each side, and do resyncs count?

## MP02 iPaaS (Zapier, Make, n8n, Workato)

Passes only if every one of these holds:

- Bounded: 10 actions or fewer per run and 2 error branches or fewer. Beyond that, a small service is easier to operate than a chain of compensating branches.
- Durable idempotency: a Make data store or n8n data table with a unique key, or a destination upsert on a unique field. Zapier has no documented ledger this skill relies on, so Zaps use destination upserts.
- Retries cannot duplicate effects (check every create step against the ledger or an upsert).
- Run history is kept long enough and read by a named operations owner.
- Cost fits the plan with 30% headroom: monthly runs x billable steps x 1.3 at or under the plan allowance. The 1.3 covers replays and growth.
- The trigger interval is inside the freshness target. Polling intervals differ by plan; read the current figure from the vendor before relying on it.

Cost notes:

- Zapier counts tasks. Replaying a whole run starts at the trigger, uses the current published Zap and counts tasks again. Each successful Zapier MCP tool call costs two tasks; failed calls cost none.
- Make bills per module run (operations or credits, by plan). Automatic retries of incomplete executions run modules again.
- n8n bills or limits executions, by plan or by your own hosting.

Worked cost check: 600 events a day x 30 days = 18,000 runs. With 6 billable steps per run that is 108,000 steps, and with the 1.3 headroom 140,400. A plan allowing 100,000 tasks a month fails MP02 on cost even before reliability.

Export checks: run `scripts/check_ipaas_export.py` on n8n workflow JSON or a Make blueprint. For tools whose exports this script does not read (Zapier, Workato, Boomi), review each step by hand:

- Trigger: event or polling, and what counts as one trigger (one record, one change, one batch).
- Filter and Paths placement: a Zapier history replay never re-runs Filter or Paths steps, so eligibility must also be enforced in the action step.
- Create steps: idempotency evidence (upsert, lookup on a unique field written by this flow, or an Idempotency-Key).
- Retries: autoreplay or retry settings and whether the step is safe to repeat.
- Errors: who is notified, after which attempt, and where failed runs wait.
- Run history: retention and who reads it.

## MP03 Event pipeline (Segment, RudderStack)

- Passes when the same event goes to two or more destinations with shared identity and consent handling.
- Fails for current-state CRM fields: an event stream does not maintain "the account's billing status now". Use reverse ETL or custom code for state.
- Segment loads warehouses in bulk, so warehouse latency is not destination latency.
- GA4 Measurement Protocol supplements client collection; it never replaces the supported web or app collection.

## MP04 Warehouse plus reverse ETL (Hightouch, Fivetran Activations, Segment Reverse ETL)

- Passes when the value needs joins or modelling across sources, or the same field must reach two or more tools.
- Freshness target must be at least warehouse load latency plus model build time plus sync interval. Below 15 minutes, prefer an event-driven path.
- Never use it for send-time suppression checks; suppression is read at the sender at send time.
- Map the contract to the tool's mode before building:
  - Update-only contract: Hightouch update mode, Fivetran Activations Update Only.
  - Upsert contract: Hightouch upsert, Activations Update or Create.
  - Current membership (people must leave when they stop qualifying): Hightouch add or remove (or all, the mirror mode), Activations Mirror. Both delete or remove downstream, so the minimum-population guard applies.
  - Events: Hightouch event syncs are insert-only and need a unique event key or events are omitted; Activations Append Only.
- Hightouch field mappings send nulls by default; skip-null and fill-empty settings apply per mapping and not to nested properties.
- Fivetran Activations filters invalid records (null identifiers, duplicates) before syncing and needs a sync key unique on both sides.

## MP05 Custom code

Required when any one of these holds:

- Per-record version or effective-time checks before writing.
- A create whose outcome can be unknown after a timeout: orders, invoices, tasks with response targets.
- Per-account serialisation.
- Volume that breaks iPaaS economics (MP02 cost check fails).
- Security needs the iPaaS cannot meet.

Minimum operating kit, all required before go-live:

- Managed runtime (never a laptop or a personal account) with a deploy pipeline and an owner.
- Queue between receipt and processing.
- Idempotency store with unique constraints.
- Secrets manager; no secrets in code, config files or environment dumps.
- Structured logs without payloads or personal data.
- Alerts routed to on-call, and a runbook.

## MP06 File exchange

Only when a system exports files and nothing else. Requires:

- A completion marker (the receiver never reads a file still being written).
- A batch ID per file and a duplicate-batch check.
- A declared schema and row counts checked on load.
- A late-file policy: what happens when the file misses its slot.
- A way to learn about deletions and eligibility losses: an incremental file with no row for a customer cannot distinguish unchanged from removed. Ask for a full snapshot or explicit removal rows.

## MP07 MCP access

- Only for investigation and reviewed one-off operations.
- The protocol supplies no scheduler, retry ledger or delivery guarantee. Using MCP as the trigger or delivery path for a recurring flow is MS01 FAIL.
- Read-only routes per tool: [handoffs.md](handoffs.md).

## Pattern cards

Form submission to CRM lead and owner task:

- Default method: the native form integration when it dedupes and creates the task; otherwise iPaaS with a data store, or custom code.
- Keys: submission_id for the event; one task per submission_id; person matched by email with a crosswalk to the CRM ID.
- Must have: trigger on the accepted submission (not the click), a versioned owner rule, unknown-outcome reconciliation before creating the task again, a routing exception queue.
- Watch: retries on create, email used to match accounts, retries pushing past the speed-to-lead target.

Billing state to CRM account summary:

- Default method: custom webhook receiver plus worker, or the native app if it aggregates several subscriptions and rejects stale versions.
- Keys: account ID in a unique CRM property; an integration-owned revision; refetch current subscriptions on each event. Include the billing account and live or test environment in keys when there are several.
- Must have: aggregation rule across subscriptions, version check, update-only with exceptions for unmatched accounts, explicit clear only when no active subscription remains.
- Watch: the last-arriving event winning, domain matching, a renewal date with two writers.

Product usage or score to CRM (PQL):

- Default method: warehouse model plus reverse ETL, update only, on a unique account key.
- Keys: account ID; model version.
- Must have: known positive, known negative and unknown states; removal behaviour when an account stops qualifying; freshness set by the sales decision.
- Watch: null overwrite from failed joins, add-only syncs, a score field people can edit.

CRM to CRM (HubSpot and Salesforce):

- Default method: native connector with per-field direction.
- Keys: the connector's pairing, plus a crosswalk in the warehouse for audits.
- Must have: owner and direction per field, inclusion rules understood (they govern initial inclusion; synced records keep syncing), an API budget for resyncs.
- Watch: global winner settings, picklist mismatches, validation rules rejecting writes, duplicate rules and record locks on bulk loads, mapped fields hidden from the integration user, resyncs starving other apps.

Enrichment to CRM (Clay, Apollo):

- Default method: write to enrichment-owned fields, or fill empty fields only.
- Keys: the CRM record ID carried through the enrichment table.
- Must have: separate update and create paths, refresh cadence set by the decision, no trigger on the integration's own writes.
- Watch: loops from enrichment timestamps, overwritten owners, duplicate companies from create-on-miss.

Unsubscribe or consent change to every sender:

- Default method: each sender's preference API, event-driven.
- Keys: person ID plus subscription type and brand scope.
- Must have: durable capture, scope mapping, a read-back that proves enforcement, optional sends held until acknowledged, resubscribe only on a new explicit action.
- Watch: profile updates carrying default subscription values, stale mirrors resubscribing people.

Conversions to ad platforms:

- Default method: server-side or warehouse upload through the platform's supported API.
- Keys: Meta event_id plus event_name (deduplicated within 48 hours); Google Ads order_id per conversion action.
- Must have: partial-failure handling, a ledger kept longer than the platform's dedupe window, test or validate-only mode first.
- Watch: missing click IDs, re-uploads after the dedupe window, deprecated upload endpoints (since 15 June 2026 Google Ads UploadClickConversions fails for developer tokens that sent no offline conversion uploads between 17 December 2025 and 15 June 2026; Google points to the Data Manager API; upload page, checked 2026-11-06).

Warehouse audiences to ad and lifecycle tools:

- Default method: reverse ETL add and remove.
- Keys: identifiers hashed per platform rules; audience ID.
- Must have: a current-membership contract with removals, the minimum-population guard, channel eligibility kept separate from qualification.
- Watch: clear and fill removing members other tools wrote, an empty source mirrored as a mass removal, and the Google Customer Match change: as of 1 April 2026 most new integrations must use the Data Manager API, and existing eligible integrations can stay on the Google Ads API (Customer Match page, checked 2026-11-06).

CRM deal to ERP or billing order:

- Default method: custom code with a single owner of order creation.
- Keys: an immutable hand-off key per intended order; ERP external ID upsert.
- Must have: states approved, submitting, acknowledged and needs reconciliation; an atomic claim; a search of the ERP before any re-create.
- Watch: duplicate orders after timeouts, a return sync failing silently, salespeople clicking create again.

## Worked rationale: Acme's billing flow

- MP01 fails: the native Stripe app syncs subscriptions as records but cannot publish one company summary across several subscriptions or reject a stale version.
- MP02 fails: serialising writes per account and comparing an integration revision need code steps the operations team cannot maintain inside the scenario.
- MP03 fails: the destination needs current state.
- MP04 fails: the 15-minute target is below load plus build plus sync time.
- MP05 passes: version checks, per-account serialisation and an unknown-outcome check are exactly its triggers. Full spec: [../assets/example-acme-integration-spec.md](../assets/example-acme-integration-spec.md).
