HubSpot Webhooks for Outbound: The Journal Model, Scopes, and the Limits That Bite
HubSpot's v4 webhooks are polled rather than pushed. What that changes, which scopes you need, what an event looks like, and the rate limits that decide your design.

HubSpot now runs two webhook systems. The v4 journal API has no targetUrl field, so your app polls an ordered event log instead of hosting a receiver. It needs OAuth plus both journal scopes and object scopes, and it enforces per-app rate limits of 100, 50 and 10 requests per second.
Key takeaways
- The v4 webhooks endpoints carry no targetUrl field, because subscriptions are written to a journal the app polls rather than pushed to a URL.
- Authorisation needs journal scopes and the object scopes for what you subscribe to; journal scopes alone produce a feed that returns nothing useful.
- Published per-app rate limits are 100 requests per second for the journal API, 50 for subscriptions and 10 for snapshots.
- Journal URLs expire after a set time, so payloads must be downloaded in the same pass that discovers them.
Reviewed and updated August 15, 2026
HubSpot Webhooks for Outbound: The Journal Model, Scopes, and the Limits That Bite
The usual reason an outbound team goes looking for HubSpot webhooks is that a rep replied to a prospect who was already a closed-won customer, and everyone agrees this should never happen again. The fix is an event feed: something in HubSpot changes, the sending side hears about it, the list stops going out. Simple enough to describe, and the first surprise is that HubSpot now has two webhook systems that do not talk to each other, and the newer one is not a webhook in the shape most people mean.
Two systems, and the one that no longer pushes
HubSpot's developer documentation for the webhooks journal and v4 management APIs, last modified 5 June 2026 and marked BETA, is explicit that these APIs provide a new subscription model and are not currently compatible with the previous version of the webhooks API. The line that decides your architecture is the next one: the v4 endpoints do not include a targetUrl field, because subscriptions are written to a journal that is polled by the app.
That inverts the integration. A classic webhook is push: HubSpot posts to a URL you host, and you need a public endpoint, a queue and a retry story. The journal model is pull: HubSpot writes events into an ordered log and your app walks it. You need a scheduler and somewhere to keep a cursor, and you no longer need an internet-facing receiver at all.
Neither shape is better in the abstract, and the choice is usually made for you by what you already run. A team with a receiver in production, of the kind described in the Smartlead API, is set up for push and can keep that pattern on the older API. A team with no public endpoint and a nightly job already running gets to skip the hardest part of webhook engineering by polling a log instead.
- Requires a public HTTPS endpoint you operate
- You handle availability, retries and ordering on receipt
- Managed alongside app feature components
- Familiar shape if you already run a receiver
- No targetUrl field exists on the subscription
- Events land in an ordered journal your app walks
- No inbound endpoint to host or secure
- Managed by API only, because subscriptions are install-specific
Authentication and scopes, which is where most first attempts stop

Every endpoint requires OAuth 2.0. The journal API uses a client credentials token to authorise actions on behalf of your app, obtained from https://api.hubapi.com/oauth/v1/token and sent as a bearer token in the Authorization header.
The scope list is granular and split in two, which is the part that catches people. HubSpot documents these management scopes:
developer.webhooks_journal.readfor journal datadeveloper.webhooks_journal.subscriptions.readand.subscriptions.writefor retrieving and managing subscriptionsdeveloper.webhooks_journal.snapshots.readand.snapshots.writefor snapshot information and object snapshots
On top of those, you must also authorise the scopes matching the object types you are subscribing to. HubSpot's example is that subscribing to contact change events requires crm.contacts.read as well. An integration authorised only for the journal scopes will create subscriptions cleanly and then receive nothing useful, which reads exactly like a broken feed rather than a missing permission.
What an event actually looks like
Journal responses carry an offset and a journalEvents array. A property-change event on a contact carries a type, the portalId, an occurredAt timestamp, an action such as UPDATE, an objectTypeId (0-1 for contacts), the objectId, and a propertyChanges object naming the changed properties and their new values. The response as a whole carries a publishedAt timestamp.
Association events are shaped differently and are worth reading before you write a parser that assumes one shape. They carry fromObjectId and toObjectId, fromObjectTypeId and toObjectTypeId, an associationTypeId, an associationCategory such as USER_DEFINED, and an isPrimary flag, with the action ASSOCIATION_ADDED. App lifecycle events arrive as app_lifecycle_event with their own event type IDs: 4-1909196 for an app install and 4-1916193 for an uninstall.
For a suppression use case, the property-change event is the one that carries weight. A lifecycle-stage change to customer, an opt-out property flipping, an owner being assigned: each of those arrives as a named property in propertyChanges, which means the consumer can filter on the property name rather than re-fetching the whole record to work out what moved.
The third piece of the v4 surface answers the question every event-driven integration eventually asks, which is what the world looked like before the events started. HubSpot documents a snapshots API for creating CRM object snapshots, with its own read and write scopes and its own rate limit. The intended division of labour is worth stating because it is easy to get backwards: snapshots establish the starting state, and the journal carries the changes from that point on. HubSpot's guidance on snapshots is to batch multiple requests into single API calls, request only the properties you need, confirm the objects exist in the specified portal before asking for them, and remember that a snapshot reflects object state at request time rather than at any other moment. An integration that tries to reconstruct current state by replaying the journal from the beginning is doing the snapshot's job slowly and against the tightest rate limit of the three.
The limits, and the four operational facts around them

HubSpot publishes per-app rate limits for these APIs, and states that they are distinct from HubSpot's other rate limits and subject to change during the beta:
- Journal API: 100 requests per second per app
- Subscriptions API: 50 requests per second per app
- Snapshots API: 10 requests per second per app
Exceeding them returns 429 Too Many Requests with a Retry-After header naming when to retry. The documented error set otherwise runs 200 and 204 for success, then 400, 401, 403, 404 and 500, with error bodies carrying a status, message, correlationId and category. Keeping the correlation ID is worth the two lines of code: HubSpot's own guidance is to quote it when contacting support, and it is the difference between a reproducible ticket and a description of a feeling.
Four operational behaviours in HubSpot's best-practice section change how you build the consumer, and they are easy to miss because they read like generic advice:
Process the journal in chronological order using the offset system, and store the currentOffset from responses. The cursor is yours to persist. Lose it and you either replay history or skip it, and both are visible downstream as duplicate or missing suppressions.
Journal URLs expire after a set time, so download files promptly. This is the one that breaks a scheduled job quietly. A worker that fetches a batch of journal URLs, queues them, and processes the queue an hour later can find the URLs dead on arrival. Fetch and store the payloads in the same pass that discovers them.
Filter at the subscription rather than at the consumer. HubSpot's guidance is to use object type and action filters to reduce unnecessary events, and to subscribe only to the properties you need. A subscription on every contact property in a hundred-thousand-contact portal generates a volume of noise that will find your rate limit for you.
Remove unused subscriptions. Subscriptions accumulate across development, and HubSpot names regular cleanup as a maintenance practice. An abandoned subscription from a prototype is still generating journal volume that counts against you.
- Step 1Subscribe narrowly
Object type and action filters, plus only the properties that change a send decision. Authorise both the journal scopes and the object scopes.
- Step 2Walk the journal
Poll in chronological order, download payloads in the same pass that discovers their URLs, and persist the currentOffset before acting on anything.
- Step 3Filter on propertyChanges
Match on the named property rather than refetching the record. Lifecycle stage, opt-out and owner changes each arrive named.
- Step 4Write the suppression
Push the affected addresses into the send-side exclusion list before the next build, so the change lands ahead of the campaign rather than behind it.
What this is worth, and what it does not fix

An event feed from the CRM into the sending side removes an entire class of embarrassment: contacting a live customer, a live opportunity, or someone who opted out last week. It is the highest-value thing most outbound teams build against a CRM API, and it is worth building before any of the more interesting automations, because everything else is upside while this one is downside.
What it does not fix is targeting. A clean suppression list makes sure you do not email the wrong people; it does nothing to find the right ones, and no volume of event plumbing substitutes for the list being built against a real profile in the first place. The enrichment half of that is covered in CRM enrichment and Clay enrichment, and the record-collision problem that shows up the moment two systems write to one CRM is covered in Apollo and HubSpot.
Nor does it change the shape of the campaign it protects. One message per campaign, no bumps, no thread replies: a webhook that tells you a prospect went quiet is not a licence to send again, because the second message lands under the first and reads as a chase whatever triggered it. The right use of a quiet signal is a different campaign with a different premise, later, if the account still merits one.
For teams still choosing where the records should live, HubSpot CRM vs Pipedrive and best CRM tools for cold email teams cover the platform question, and best HubSpot CRM alternatives covers the exit. If you want the list built and sent without wiring any of this yourself, see what that looks like.
One documentation note that saves a search: HubSpot's developer docs publish a complete index at /docs/llms.txt, which is the fastest way to find the current page for an endpoint when a bookmarked URL has moved.
Pricing and features verified as of August 2026. Verify current terms with the vendor before relying on them.
Frequently asked questions.
Frequently asked questions- Do HubSpot webhooks push to my server or do I poll them?
- Both exist. The previous version pushes to a URL you host. The v4 journal API does not include a targetUrl field at all, because subscriptions are written to a journal that your app polls. HubSpot states the two are not currently compatible, so the choice is architectural rather than a version bump you can apply later.
- What scopes does the webhooks journal API need?
- Two sets. The management scopes are developer.webhooks_journal.read, the subscriptions read and write scopes, and the snapshots read and write scopes. On top of those you must authorise the scopes matching the objects you subscribe to, so contact change events also need crm.contacts.read. Missing the second set looks exactly like a broken feed.
- What are HubSpot's webhook rate limits?
- HubSpot publishes 100 requests per second per app for the journal API, 50 per second for the subscriptions API and 10 per second for the snapshots API. Exceeding them returns 429 with a Retry-After header. HubSpot notes these are distinct from its other rate limits and are subject to change during the beta period.
- Can I use webhooks to trigger follow-up emails to prospects?
- Technically yes, and it is worth separating the plumbing from the decision. An event feed is ideal for suppression, keeping live customers and opted-out contacts out of a send. Using a quiet signal to trigger another message is a different matter: a follow-up lands beneath the message already ignored and reads as a chase whatever triggered it.
About the author.
B2B cold email experts helping companies generate qualified leads through done-for-you outreach campaigns.
RevenueFlow Team
Explore more.
Ready to scale your outreach?
We build GTM engines that book real meetings. See the receipts.
Related articles.
The Smartlead API: Webhooks, Rate Limits, and Reply Handling
Smartlead authenticates by query parameter and does not publish rate limits. What that means for how you build, plus the six webhook events and how to handle replies.
Calendly API: What You Can Automate and What You Cannot
Read access runs on any plan, webhooks need a paid one, and programmatic deletion is Enterprise-only. The scope decision that quietly breaks integrations, first.
The Instantly.ai API: Rate Limits, Scopes, and What You Can Automate
Instantly publishes exact rate limits and scopes its API keys. What the workspace-level limit means for your design, and the asynchronous pattern to get right.
The Apollo.io API: Rate Limits, Credit Burn, and What to Cache
Apollo's published rate limits and per-endpoint credit costs, what the asymmetry between them implies for job design, and the caching that stops you paying twice.
Apollo and HubSpot: The Deduplication Rules That Decide If You Get Duplicates
HubSpot matches contacts on email and companies on domain. Every duplicate traces back to a record arriving without that field or carrying a different value in it.
Pipedrive Integrations: Deciding Which System Owns Each Field
Every Pipedrive integration failure that costs money comes from two systems writing one field with no rule about who wins. How to settle that first.