Someone has proved the idea works. A model was shown a few records, it produced a decent answer, and the question has moved from whether to how. That is the point at which the money gets committed, and the decision that commits it is not which model you use.
It is the shape of the integration. Six shapes are in common use, and they differ by orders of magnitude in what they cost to build, what they cost to run and what they do to you when they go wrong. A project that quietly gets switched off has almost always been given a shape chosen for ambition rather than for the job. A worked example of this shape, in distribution, is in AI order entry for distributors.
None of this is CRM-specific. These are properties of the boundary between a probabilistic system and a system of record, so they hold whether you run HubSpot, Salesforce, Dynamics or something built in-house. Where a concrete detail helps, the examples are HubSpot, because that is where our depth is.
Six shapes, and most of the work belongs in the first three
Read them in order and stop at the first that does the job. The common failure is starting at pattern four.
Before any of them, one question. A good deal of what gets commissioned is now covered by a subscription you already hold. ChatGPT's workspace agents run on a schedule or a trigger, run while nobody is logged in, and take approvals on write actions, with nobody writing code. Most CRMs now ship their own AI as well. If the requirement is well-trodden and the output can land where a person will read it, buy it rather than build it. The routes for doing that against a CRM are covered in connecting an OpenAI agent to HubSpot. What follows applies once you have established that a subscription does not cover the job.
1. Read-only enrichment is the one people skip on the way to something worse
The agent reads the CRM, produces an answer or a draft, and a person acts on it. Account briefs before a call, a first draft of a reply, a shortlist of accounts matching a description nobody can express as a filter.
What it costs. Least of the six, by a wide margin. There is no write path, so there is no rollback plan, no approval flow, no property allow-list and no reconciliation job. Days rather than months.
How it fails. Silently, by omission. It reads a partial view, or a stale record, and produces a confident brief built on it. There is no error, only a slightly wrong answer that looks exactly like a right one. The mitigation is not technical: cite the records the answer came from, so the person acting on it sees the basis in the same glance.
This pattern gets skipped because it does not feel like automation. Much of the value people describe when they ask for an agent is available here, at a fraction of the cost of the thing they asked for.
2. Human-in-the-loop write is the right default, and its cost is a person's attention
The agent proposes, a person approves, the system writes. The default for anything touching money, pipeline stage, ownership or a customer-visible communication.
What it costs. Review time, per item, scaling linearly with volume, which is usually the thing automation was meant to remove. OpenAI's Responses API requests approval before data is shared with a connector or remote MCP server by default, so the mechanism is free. The reviewer is not.
How it fails. When throughput exceeds the reviewer. Work out the review rate before you build: if the agent will propose 300 changes a day and a person can properly assess forty, the pattern has already failed and no amount of interface polish fixes it. The architectural answer is to narrow what routes to a human by rule, so the obvious ninety per cent does not consume the attention needed for the other ten.
Design it so you can graduate out of it, one property at a time, on evidence from its own approval history.
3. Autonomous write with constraints is where the value lands and where the engineering goes
Scoped to named objects and properties, validated against a schema, reversible, logged. This is the pattern that pays for itself, and it is most of the build effort in a typical project.
What it costs. Almost everything in this article: the property allow-list, the schema enforcement, the idempotency, the audit trail, the reconciliation, the kill switch. None are features you can add in a later phase, because each changes the shape of the code that writes.
How it fails. At the edges of the constraint you wrote rather than in the middle, and at volume, because nobody is watching. The controls that make it survivable are a governance question as much as an engineering one, and they are set out in governing AI write-access to your CRM.
4. Event-driven: webhooks and polling both have a catch, and it is not the one you expect
The CRM emits a change, the agent responds. Suits work that has to happen close to when the change happens.
Webhooks give low latency and cost nothing when nothing is happening. In exchange you inherit the vendor's delivery semantics, and they are weaker than most people assume. HubSpot retries a failed webhook notification a maximum of ten times, spread over a 24 hour period. OpenAI's own webhooks retry for up to 72 hours with exponential backoff, and its documentation states plainly that it "may deliver duplicate copies of the same webhook event", recommending the webhook-id header as an idempotency key. Treat that as the general case rather than a quirk: you will receive the same event more than once, and in an order that does not match the changes that caused them.
An operational trap sits alongside it. Vendors expect a fast acknowledgement, so the handler must acknowledge and queue rather than work inline. Handlers that process inline are slow, so they time out, so they get retried, which is how a system manufactures its own duplicates.
Polling has a worse reputation than it deserves for CRM work. No endpoint to expose, no signature to verify, and ordering is yours to control. A cursor you own is far easier to reason about than a delivery guarantee you do not. The costs are real but bounded: latency equal to your interval, and rate-limit budget spent discovering that nothing changed. If a change mattering five minutes later is acceptable, and for most CRM work it is, polling is cheaper to run and much cheaper to debug.
How it fails. Ordering and duplicates, discovered late. Typically the first time two changes to one record land in the same second, a scenario that never occurs in testing and occurs daily in production.
5. Batch is the cheapest shape to run and the most under-used
Bulk work where nobody is waiting: enrichment, classification, backfilling a field, scoring a segment.
What it costs. Less than anything else per unit of work. OpenAI's Batch API is built for "processing jobs that don't require immediate responses", completes each batch within 24 hours, carries a stated 50% discount against synchronous calls and runs against a separate pool of significantly higher rate limits. If the work does not need an answer now, running it synchronously is a decision to pay more for nothing.
How it fails. On the write side, never the read side. Four thousand good answers arriving at once is where the rate-limit problem and the idempotency problem turn up together, and a batch re-run after a partial failure is the retry case in its purest form. Design the write phase as a separate throttled, resumable step, not a loop at the end of the read phase.
6. Agent-initiated retrieval is the most capable and the hardest to reason about
The agent decides what to fetch, through tool calling or an MCP server. Suits open-ended questions where you genuinely cannot predict what needs reading.
What it costs. Certainty. With the first five patterns you can write down every call the system is capable of making. Here you cannot, so control moves from the list of calls to the list of tools and the budget. Cost per run becomes variable and unbounded unless you cap it, and debugging is harder because the same question can take a different path on different runs.
The platform controls are worth using. allowed_tools limits the subset of an MCP server's tools the model can see at all, and approval is required by default. Note too that OpenAI does not store the authorization value you pass for a remote MCP server, so it must be sent on every request: token lifecycle and rotation are entirely yours.
How it fails. It asks a reasonable question you did not anticipate. The classic is a query that behaves perfectly against a test account holding a few hundred records and pulls tens of thousands on the client's.
| Pattern | Suits | Main cost | Characteristic failure |
|---|---|---|---|
| Read-only enrichment | Briefs, drafts, research | Days of build | A confident answer from a partial view |
| Human-in-the-loop write | Anything consequential | Reviewer attention, linear in volume | Throughput exceeds the reviewer |
| Autonomous write | High volume, narrow, checkable | Most of the engineering | Edge cases, at volume, unobserved |
| Event-driven | Work tied to a change | Delivery semantics you do not control | Duplicates and out-of-order events |
| Batch | Bulk work nobody waits for | Least per unit | The write phase, not the read phase |
| Agent-initiated retrieval | Genuinely open-ended questions | Predictability and cost control | A reasonable question with an enormous answer |
A retry that creates a second record is the defect you will actually ship
This is the most common production defect in CRM integrations. It is present in most first builds and invisible in every demo.
The cause is that every layer retries independently, and each layer is individually correct. The network times out after the write succeeded but before the response came back, so the client believes it failed. The webhook is redelivered because your handler was slow. The queue redelivers because a worker crashed. The batch is re-run after a partial failure. Compose four correct behaviours and you get duplicates.
A CRM is the worst possible destination for this, because a duplicate is not a harmless extra row. A duplicate contact splits the activity history in two, so neither copy tells the truth. It can break routing and ownership, and it can get the same person emailed twice by a sequence. Because create endpoints are not naturally idempotent, nothing in the platform prevents it.
Three things fix it, in order of preference.
Upsert against a natural key where the CRM offers one, so the second call updates rather than creates.
Derive an idempotency key from the source event where it does not. The key must come from the event itself: the message ID, the source record ID plus a change timestamp, or the vendor's own event identifier. A UUID generated at the point of the call defeats the entire purpose, and that mistake is made often enough to be worth naming. Record the key before the write and check it before the write.
Use the header where the API provides one. OpenAI names webhook-id for deduplicating its own deliveries. Where a vendor gives you an idempotency mechanism, it is because they expect duplicates.
Then test it deliberately. Send the same event through twice on purpose and assert that one record exists. That takes an hour to write and belongs in the test suite rather than the plan, because a defect nobody tests for is a defect that ships.
The naive bulk job takes down your own integration first
There are two ceilings and they behave differently.
On the model side, OpenAI returns a 429 when a request exceeds a temporary rate limit, with a Retry-After header its documentation says to treat as a minimum, plus "a small random delay so multiple clients don't retry at the same time". The sentence that matters most: "unsuccessful requests contribute to your per-minute limit, so continuously resending a request won't work". A naive retry loop converts a brief rate limit into a sustained outage of your own making.
On the CRM side the limit is usually shared across the whole account rather than per integration. That is the detail that catches people, because your bulk job is not competing with itself. It is competing with your warehouse sync, your marketing automation and every other integration you own, and when it wins, they fail.
So the bulk job written the obvious way, reading ten thousand records and fanning out as fast as the loop runs, does not trouble the vendor. It exhausts your allowance and takes your other integrations with it, usually at month end when everything else is busy. What prevents it: a queue with a concurrency cap you chose deliberately, backoff with jitter, a dead-letter path so one poisoned record does not stall the run, and bulk work routed to the batch path so it never competes with anything interactive.
Your JSON schema and your CRM's field definitions are two sources of truth
Ask a model for a close date and it may return "end of the quarter". Ask for a lifecycle stage and it may return "probably a marketing qualified lead". Both are good English and both break whatever parses that field next.
Structured Outputs solves the immediate problem: with strict: true the model's answer must adhere to a supplied JSON Schema, so it cannot omit a required key or invent a value outside an enum. A safety refusal bypasses the schema and returns a refusal field instead, which is programmatically detectable and needs its own branch rather than being treated as a malformed response.
The architectural point sits a level above that. Your JSON schema enforces the shape you declared, not the shape the CRM field actually has, and the two drift. Someone adds a picklist value in the CRM and your schema rejects it. Someone tightens a field and your schema keeps producing values it no longer accepts. Generate the schema from the CRM's live field definitions rather than hand-writing it, and you have one source of truth instead of two that agreed only on the day they were written.
How much this matters varies by pattern. In read-only enrichment a human reads the prose and nothing parses it. In autonomous write and batch it is the difference between a working integration and a field full of sentences.
Reconciliation answers a different question from monitoring
Monitoring tells you what went wrong. It does not tell you what never happened, and that is the failure class that runs longest before anyone notices.
Consider what produces no error at all. A webhook that was never delivered. A record excluded by a filter condition nobody remembers writing. A batch item dropped between phases. A scheduled run that never started. In every case the dashboard is green, the error rate is zero, and the work did not happen.
The fix is a ledger of expected work rather than a log of completed work. Every source event gets a row when it arrives, with a state, and the row closes only when the work is done. Then compare periodically: the population that should have been processed against the population that was, with an alert on the gap.
Alert on silence too. A job producing zero output should be as loud as one that fails, and by default it is completely quiet. The cheapest useful version is a daily count of source events against writes, with a threshold. An afternoon's work, and it catches the class of fault that otherwise runs for a month.
Your CRM's test environment is worse than you are assuming
Most CRMs make this awkward, and pretending otherwise produces plans that cannot be executed. HubSpot is a fair example, and more generous than several competitors.
Standard sandboxes are Enterprise-only, so a client on Professional has no sandbox at all. That is a constraint on the architecture, not an inconvenience for the developer. Where a sandbox does exist it copies the structure of the production account plus a slice of data: an optional one-time copy of your 5,000 most recently updated contacts, plus up to 100 associated deals, companies and tickets for each of those contacts. Your test set is therefore not your data, and the records that break integrations are the strange ones, which is precisely what a slice of recently updated records omits.
Two further details matter more than the volume. Production integrations are not connected to the sandbox automatically, so the environment you test in lacks the other systems that make production behave as it does. And HubSpot states plainly that contacts, companies, deals, tickets and custom objects have different IDs between sandbox and production. Anything keyed on a record ID has to be remapped, test fixtures do not travel, and a configuration file full of IDs becomes a production-only artefact that has never been tested anywhere.
Developer test accounts are the other option, up to ten of them, free, with 90-day access to many enterprise features, but they hold test data only and cannot sync with another account.
The consequence for design is the point. Because you cannot faithfully rehearse, production is your first real test, so the architecture has to make first contact narrow and reversible. Ship the single-record path before the bulk path. Run the first live executions against an explicit allow-list of record IDs. And make the bulk path literally the single-record path in a loop rather than a separate faster implementation, because the separate faster implementation is the one nobody has reviewed record by record. Budget real time for building representative bad data in the sandbox too. That work is not overhead, it is the test suite.
How to decide this cheaply
Pick the earliest of the six shapes that does the job, and write down what evidence would justify moving to the next one.
That means four questions in order. Does a subscription you already pay for cover this? If not, can it be read-only, given that read-only carries much of the value at a fraction of the cost? If it must write, can a person review at your actual volume? Only when all three answers are no does autonomous write earn its budget, and even then it earns it one property at a time.
The questions worth putting to whoever builds it, in-house or otherwise, are not about the model. Which of the six shapes is this. What happens when a call is retried. How would we find out what did not happen. Where does the schema come from. What does the first live run touch. A supplier with good answers to those five has built this before. One who wants to talk about model selection has not.
We are an OpenAI Select Partner and a HubSpot Diamond Solutions Partner, and we build OpenAI implementations and AI implementations on whichever platform fits the workload. We resell nothing and take no margin on your usage, which is why we would rather talk you down to a simpler pattern that still runs in two years.
This is one rung of a longer ladder, set out in our overview of OpenAI for UK business.
Request a quote, and bring the workflow rather than the model.
Stay Updated with Our Latest Insights
Get expert HubSpot tips and integration strategies delivered to your inbox.




