If you've ever tried to build a production-grade HubSpot integration in Go, you've probably hit the same wall we did: there previously wasn't a first-party Go SDK. The community options that exist tend to cover a slice of the API, are unmaintained, or feel awkward to use from idiomatic Go code.
HubSpot officially maintains SDKs for Node.js, Python, PHP, Ruby, and .NET. That covers a lot of the ecosystem — but not the growing population of backend teams running Go. At Scopious Digital, we build HubSpot integrations for our own products and for clients, and most of that backend work lives in Go services. Calling the HubSpot API through a hand-rolled HTTP layer over and over got old fast, and we kept re-implementing the same retry, rate-limit, pagination, and typed-response boilerplate in every new project.
So we did what a lot of teams eventually do: we sat down, ported the official Node.js SDK to idiomatic Go and released it as a community edition under MIT. It's called hubspot-go, and it's available now on GitHub:
github.com/scopiousdigital/hubspot-go
*Disclaimer: This is not a HubSpot-built, maintained, or supported SDK and is shared without implied warranty.
The big point of discussion internally was - do we port the SDK as close to 1-1 or do we shift and take a more go-centric approach. Although we debated this a lot and are still unsure we made the right decision, we decided to port it as close to 1-1. The main benefit of this is familiarity and ease of maintenance. When HubSpot makes a change - we make a change.
Who this post is for
This post is about why Go is a great fit for HubSpot workloads, the design decisions that shaped the port, and how to go from go get to a working contact create → search → update flow in about five minutes. If you're a HubSpot developer who has been Go-curious, or a Go developer who has been fighting the HubSpot REST API by hand, this is for you.
This guide assumes you're comfortable with:
- Writing and running a basic Go program (Go 1.21+)
- HubSpot concepts like apps, authentication, and CRM objects
- Reading API reference docs
You don't need prior experience with the Node.js SDK — but if you have it, you'll find the mental model almost identical. Object shapes, method names, and semantics were kept close to the Node SDK on purpose, so existing HubSpot developers and AI coding assistants trained on HubSpot content can move between languages without relearning the API.
Why Go is a particularly good fit for HubSpot integrations
Most HubSpot integrations are essentially data-movement problems: sync contacts, listen for webhooks, batch update deals, reconcile properties with a data warehouse. These workloads are exactly where Go shines, for three reasons.
Concurrency is a native concern. Go routines and channels make it natural to fan out batch requests, parallelize webhook processing, or run multiple syncs on a schedule without reaching for external runtimes or orchestrators. When you're dealing with HubSpot's per-second rate limits and 100-item batch ceilings, being able to compose concurrency primitives cleanly is a real productivity win.
Type safety catches mistakes at compile time. HubSpot CRM properties are a bag of loosely-typed strings under the hood, but the shapes of requests and responses are well-defined. A typed SDK means you find out at compile time that you passed a FilterGroup where a Filter was expected, not in the production logs.
Deployment is boring in the best way. A single statically-linked binary with no runtime to install or patch makes ops much simpler than shipping a Node service. For internal sync jobs, webhook listeners, and CLI tools, this is a meaningful operational upgrade.
None of this is news to Go developers — but it was the specific combination of these three things that finally pushed us to stop working around the missing SDK and build it properly.
What's in the box
The library is a direct port of the official HubSpot Node.js SDK, restructured to feel native to Go. At a high level, you get:
- A top-level hubspot.Client with functional options for configuration
- Domain-organized subpackages: crm, cms, marketing, automation, settings, OAuth, webhooks, files, and communication preferences
- Full CRUD + batch + search coverage for 18+ CRM object types, with identical method signatures across all of them
- Built-in automatic retries for 5xx and 429 responses
- Client-side rate limiting with configurable requests-per-second and burst
- Auto-pagination helpers for list endpoints
- Typed error helpers like hubspot.IsNotFound and hubspot.IsRateLimited
Two of these deserve a closer look before we get to the tutorial, because they're where the SDK earns its keep.
Full CRM coverage, uniformly
HubSpot's CRM API uses the same shape of requests across object types — contacts, companies, deals, tickets, products, line items, quotes, calls, emails, meetings, notes, tasks, leads, and more. The SDK follows that same symmetry. Every CRM object exposes the same set of methods: Create, GetByID, Update, Archive, List, GetAll, Search, BatchCreate, BatchRead, BatchUpdate, BatchArchive, and BatchUpsert. Learn the shape once, use it everywhere. Swapping client.CRM.Contacts for client.CRM.Deals in your code changes the object type and nothing else.
This matters a lot when you're building non-trivial integrations that touch multiple object types, because it compresses the API surface you actually need to remember to a single mental model.
Retries and rate limiting, built in
HubSpot rate limits are real, and 5xx blips happen. The Node SDK leaves resilience outside of rate-limiting to you; we decided the Go SDK should bake it in behind a single option each.
WithRetries(n) wraps 5xx responses and 429 Too Many Requests with exponential backoff, respecting Retry-After headers when HubSpot sends them. WithRateLimiter(rps, burst) applies a token-bucket limiter on the client side so you can stay under your tenant's quota without hand-rolling a limiter per service. The result is that a batch sync over a large list of contacts stops being a source of incidents.
Installation
That's it. There's no code generation step and no configuration file to manage.
Production-ready client configuration
Here's the configuration we actually use in our own services. Adjust the retry and rate-limit numbers to match your HubSpot tier and SLOs.
That's the whole resilience story: three lines of options.
End-to-end example: create → search → update a contact
Let's walk through the most common CRM pattern in a single program: create a contact, look them up by email then update a property. This is the shape of thousands of real integrations.
1. Create a contact
2. Search for the contact by email
3. Update the contact
That's the full lifecycle: one client, three idiomatic calls, typed responses, and real error handling. Notice how the filter syntax for search mirrors the HubSpot REST payload exactly — we kept the shape intentionally close to the JSON you'd send by hand so the mental model transfers in either direction.
Scaling up: batch operations and pagination
For anything beyond a handful of records, you'll want the batch APIs. They take up to 100 inputs per call and are covered by the same retry and rate-limit machinery.
Batch upsert by email — great for data warehouse sync jobs
For pagination, you have a choice. List gives you a single page plus a cursor if you want manual control; GetAll handles the cursor loop for you and returns every matching record. Use GetAll for one-shot jobs, and List when you need to stream or stop early.
Best practices and common pitfalls
A few things we've learned the hard way while shipping HubSpot integrations:
Always pass a context with a timeout. Every method takes ctx as its first argument. Use context.WithTimeout at the call site so a slow HubSpot response can't pin a goroutine indefinitely.
Use batch endpoints wherever you can. A batch of 100 counts as one rate-limit-consuming request, not 100. If you find yourself writing a for loop around Create, stop and reach for BatchCreate instead.
Prefer BatchUpsert with an IDProperty. For most sync flows, what you actually want is "make the record in HubSpot match my source of truth, keyed by email (or a custom unique property)." Upsert expresses that directly and avoids the create-then-update race.
Respect rate limits even with retries on. Retries handle occasional 429s gracefully, but they don't replace a sensible WithRateLimiter. If you hammer the API flat out and rely on retries, you'll spend all your throughput on backoff.
Use the typed error helpers. hubspot.IsNotFound(err) and hubspot.IsRateLimited(err) are easier to read and more reliable than matching on HTTP status codes yourself and they keep your call sites clean.
Where this goes from here
hubspot-go is a community edition, MIT licensed and maintained in the open. It already covers the surface area we depend on in production — the full CRM, CMS, marketing, automation, settings, OAuth, webhooks, and files APIs — and we're working through the long tail of remaining endpoints as we and other users hit them.
If you're a HubSpot developer who has been waiting for a real Go SDK, we'd love for you to try it, open issues and send PRs:
- Repo: github.com/scopiousdigital/hubspot-go
- Issues and feature requests: very welcome
- Pull requests: even more welcome
Our goal is a Go SDK that feels as well-supported as the first-party options in other languages — and the fastest way there is real usage from real HubSpot developers.
If you build something with it, tell us, open an issue, contribute via PR and help keep the project moving.
References and further reading
- HubSpot CRM API reference
- HubSpot private apps
- HubSpot API rate limits
- Official HubSpot Node.js SDK — the reference implementation this library is ported from
Thanks to the HubSpot developer ecosystem and to the maintainers of the official Node.js SDK, whose design and type definitions made this port possible.