Telegraft

Integration

HubSpot, and the duplicate contact problem

A HubSpot integration lets a Telegram bot create and update contacts, companies and deals in the pipeline your team already runs. The recurring problem is not the API, which is good — it is duplicate contacts, because a bot creating records from partial information will create the same person twice.

HubSpot integration: auth, limits and availability

Auth model
OAuth 2.0
Rate limit
~100 requests per 10 seconds per account, standard plans
GCC availability
No regional restriction; limits follow subscription tier
Data flow
5 hops, worker-mediated

As of 2025-10-01, Telegram Bot API 13.4

Why this integration exists

HubSpot is the CRM most GCC teams under a hundred people land on, and its API is genuinely pleasant: coherent objects, documented associations, usable sandboxes. An integration that would take a fortnight against an enterprise CRM takes days here, and the code stays readable.

What goes wrong is identity. A bot capturing a lead has a Telegram user id, a display name and whatever the person volunteered. HubSpot keys contacts on email. A lead who gives no email cannot be matched against an existing contact, so a naive integration creates a new one — and the same person enquiring twice becomes two records, two deal histories and a salesperson who thinks they are talking to a new prospect.

The fix is to decide the matching key explicitly before writing any code, and to escalate ambiguity rather than resolve it by guessing. A Telegram user id stored as a custom property gives a reliable key for anyone who has interacted with the bot before. Where that is absent and no email is given, the honest behaviour is to create the record flagged for review rather than to merge on a name, because merging two different people is far worse than holding two records for one.

How the data actually moves

Lead inTelegramqualified leadBot Workerrecord firstD1 write queuesearch then upsertHubSpot CRMwebhook on changeBot Worker
The lead is written locally before HubSpot is called, so a rate limit or an outage delays the sync without ever losing the lead.

A private app access token is the simplest arrangement for a single-portal integration and is what most of these projects use; OAuth is required if the same integration serves multiple customer portals. Either way the token is a Worker secret with the narrowest scopes that satisfy the mapping — a token with full CRM write access because it was easier to configure is a standing risk nobody revisits.

Auth model: OAuth 2.0

Their limits, and what they mean for you

HubSpot allows on the order of 100 API requests per 10 seconds per account on standard plans, with a daily ceiling.

Normal bot traffic is nowhere near it; a bulk backfill at go-live is. Migrations are paced deliberately, and the limit is per account so your bot competes with every other integration on the portal.

Contacts are keyed on email, and creating a contact without one produces a record that cannot be deduplicated later.

The matching key is decided before any code is written. A Telegram user id stored as a custom property is the reliable one for returning leads.

Associations between contacts, companies and deals are typed and must be created explicitly.

A deal created without its contact association is invisible in the views salespeople actually use. It exists, reports on it, and nobody sees it where they look.

Custom properties are referenced by internal name, which persists even when the label is changed.

Mappings reference internal names. A mapping keyed on a label breaks silently the first time an administrator tidies up the wording.

How it fails, and what happens when it does

The same person enquires twice and becomes two contacts.

Search-before-create on the agreed key, with ambiguity escalated for review rather than merged automatically. Two records for one person is recoverable; one record merging two people is not.

A rate limit is hit during a campaign spike.

Writes queue and retry with backoff. The lead is already in D1 and already confirmed to the person, so the delay is invisible to them.

A workflow on the portal fires on every bot write.

Bot writes are indistinguishable from any other API write, so existing automation runs. Mapping that automation is part of scoping precisely because the side effects are otherwise found in production.

A webhook is delivered twice for the same change.

Handlers are idempotent on the object id and version. HubSpot webhooks are at-least-once, so duplicate delivery is expected rather than exceptional.

Availability in the UAE and the wider GCC

Global

No regional restriction. API limits depend on your subscription tier rather than your location.

Free and Starter tiers

The API is available, with lower limits and fewer objects. Workable for a lead-capture bot; constraining for anything doing bulk synchronisation.

Data residency

HubSpot offers EU data hosting on some plans. Where a GCC regulator requires local residency, neither region satisfies it and that is a conversation for counsel.

Multi-portal agencies

Serving several customer portals requires OAuth rather than a private app token, which is a meaningfully larger integration.

When not to use this integration

  • Your HubSpot data is already unreliable. An integration propagates what is there faster and to more places rather than improving it.
  • You want the bot to be the primary record system. Then you do not want a CRM integration, and mixing the two produces the two-databases problem it exists to avoid.
  • Nobody has agreed what the fields mean. The mapping workshop surfaces that, and skipping it guarantees a rebuild.
  • You have heavy server-side automation on contact writes that nobody has mapped. Every bot write will trigger it.

What it runs on

ComponentVersionWhy
Cloudflare WorkerscurrentQueued writes, search-before-create and webhook handling.
Cloudflare D1currentWrite queue, sync cursors and the duplicate-review queue.
Zod4.4Validation of API responses, whose shape is not yours to control.
grammY1.45The lead capture and salesperson command surface.

Questions that come up during scoping

How do we stop the bot creating duplicate contacts?

Search before create on an agreed key — usually the Telegram user id stored as a custom property, since email is often absent at first contact. Where the match is ambiguous, the record is created flagged for review rather than merged, because merging two people is much worse than holding two records for one.

What happens during a campaign spike?

Writes queue and retry with backoff. The lead is already durable in D1 and the person has already been told it arrived, so a HubSpot rate limit delays the sync and never costs the enquiry.

Will bot writes trigger our existing workflows?

Yes. API writes from a bot are indistinguishable from any other, so every automation on those objects fires. Mapping what currently runs is part of scoping, because the surprises are otherwise discovered live.

Should we use a private app token or OAuth?

A private app token for a single portal, which covers most of these projects. OAuth is required only if one integration serves several customer portals, and it is a substantially larger piece of work.

Why do deals sometimes not appear for salespeople?

Almost always a missing association. A deal created without its contact link exists and reports correctly while being invisible in the views people actually work from, which makes it look like the write failed when it did not.

What breaks when an admin renames a property?

Nothing, if the mapping references internal names, which persist through label changes. A mapping keyed on the visible label breaks silently the first time someone tidies the wording.

Related reading