Skip to content
newThe webhook.co MCP server is live — turn any webhook into an agent event. Read the docs
test locally

Test HubSpot webhooks locally

HubSpot delivers in batches, retries for a day, and guarantees neither order nor uniqueness. Most of the surprises in local testing come from those three facts.

The loop

Create an endpoint and it prints a permanent ingest URL. Everything sent there is captured whole — headers, body, the exact bytes — before anything else happens. Stream those events to a port on your machine, and replay any one of them as often as you like while you fix the handler. Forwarding runs from your own machine, so it isn't a billable delivery.

wbhkhubspot
$ wbhk endpoints create hubspot-dev
https://wbhk.my/whep_…
$ wbhk listen <endpoint-id> --forward http://localhost:3000/webhooks
$ wbhk replay <event-id> --forward http://localhost:3000/webhooks

What's different about HubSpot

A HubSpot delivery is not an event. HubSpot batches up to 100 events into a single POST, and the five-second timeout applies to the whole batch rather than to each event inside it. Nothing in that batch is ordered either: HubSpot does not guarantee notifications arrive in the order they occurred, does not guarantee only one notification per event, and states outright that eventId is not guaranteed to be unique. You order by each notification's occurredAt millisecond timestamp and deduplicate yourself. Locally, the first symptom is a handler written for one notification being handed a batch of them.

Where the URL goes

Which kind of app you built decides where the target URL lives. In a legacy private app created through the UI: Development, then Legacy apps, your app, the Webhooks tab, Edit webhooks, Target URL, Create subscription, Commit changes. Legacy private apps built with projects do not currently support webhook subscriptions, and there is no API for managing private-app subscriptions. On the current developer platform there is no UI step — targetUrl goes in a -hsmeta.json file inside src/app/webhooks/.

HubSpot ships a test button for this. On the Webhooks tab, hover a subscription, click View details, then click Test: HubSpot sends the sample event payload listed in the panel to your configured target URL and shows your endpoint's response at the bottom. Legacy public apps have the equivalent Test this subscription flow. It is a canned sample, so read it as a shape check, not a data check.

What HubSpot sends

Subscription types read as object then action: the object lowercase, snake_case when it is two words, and the action in lowerCamelCase — creation, deletion, propertyChange, associationChange, restore, merge, privacyDeletion, newMessage. A generic form using object.creation style types also exists, but it is a public beta scoped to public apps and carries the object type in the payload's objectTypeId field.

  • contact.creation
  • contact.propertyChange
  • contact.deletion
  • company.propertyChange
  • deal.creation
  • conversation.newMessage

Worth knowing first

Five seconds covers the whole batch

The clock is per POST, not per event. If your service takes longer than five seconds to send back a response to a batch of notifications, HubSpot treats it as a failure — as it does a connection failure, or any 4xx or 5xx status. A 2xx acknowledges receipt. Design the handler around that budget, because a hundred events can share it.

Retries run for a day

Failed notifications are retried up to 10 times, spread over the next 24 hours with varying delays and per-notification randomization so concurrent failures do not all come back at once. No per-attempt schedule is published. Every payload carries attemptNumber, starting at 0, so your handler can tell a first delivery from a retry.

Settings are cached for up to five minutes

Webhook settings can be cached for up to five minutes, so a change to the target URL, the concurrency limit or the subscriptions themselves may take that long to take effect. Concurrency is 10 in-flight requests per account that installed your app, and it is an adjustable setting rather than a fixed ceiling.

Signatures are v3, with their own five minutes

Requests are signed with X-HubSpot-Signature-v3, lowercase v, alongside X-HubSpot-Request-Timestamp. Validation is a base64 HMAC-SHA256 over request method, request URI, request body and timestamp, keyed on the app's client secret and compared in constant time, rejecting anything with a timestamp older than five minutes. Two unrelated five-minute numbers, worth keeping straight.

The logs tab shows, it does not resend

Past deliveries live under the app's Logs tab, then Webhooks: a filterable history of successful and unsuccessful requests you can drill into. No redelivery or resend action is documented there. Once the 24-hour retry window closes on a delivery you missed, the log entry is the record you are left with.

The journal API is a different surface

HubSpot's newer webhooks journal is polled rather than pushed — subscriptions are written to a journal your app reads, and there is no targetUrl anywhere in it. The path /docs/api/webhooks now serves that journal documentation, so it is easy to land there while looking for push-delivery behaviour and read the wrong thing.

Signature checking is handled for you once the secret is registered — you can also check a captured signature by hand in the signature verifier.

HubSpot webhooks: common questions

Can I test a HubSpot webhook subscription without editing a record?

Yes. On the Webhooks tab of your app, hover the subscription, click View details, then click Test. HubSpot sends the sample event payload shown in that panel to your target URL and displays your endpoint's response at the bottom of the panel.

Why does my HubSpot webhook handler receive a batch instead of one event?

Because HubSpot batches. Each request can contain up to 100 events, delivered against a concurrency limit of 10 in-flight requests per account that installed your app. Handle the body as a batch of notifications, and remember the five-second response budget covers the whole batch.

Does HubSpot require HTTPS for the webhook target URL?

Yes. The target URL must be publicly available and served over HTTPS. That is why a bare localhost port will not work as a target on its own, and why the URL you register has to be one HubSpot can reach from the public internet.

How do I verify a HubSpot webhook signature?

Current requests carry the X-HubSpot-Signature-v3 header, lowercase v, plus X-HubSpot-Request-Timestamp. Compute a base64 HMAC-SHA256 over the request method, request URI, request body and timestamp using your app's client secret, compare it in constant time, and reject timestamps older than five minutes.

Can I replay a past HubSpot webhook delivery?

Not from the HubSpot UI. The Logs tab under Webhooks lets you review and filter successful and unsuccessful requests, but no resend action is documented. HubSpot's own retries are automatic: up to 10 attempts spread across the following 24 hours.

Why hasn't my new HubSpot target URL taken effect yet?

Webhook settings can be cached for up to five minutes. Changes to the webhook URL, the concurrency limits or the subscription settings may take that long to go live, so a stale delivery destination right after an edit is expected rather than broken.

Does HubSpot send a verification request to my URL before delivering?

No handshake or challenge protocol is documented. Deliveries themselves are POST requests. The testing guidance does say to have a backend service ready with GET and POST endpoints available, but it describes no challenge value your endpoint has to echo back.

Point a webhook at it. Watch it land.

Start free: a permanent URL, full inspection, one-command replay — and outbound delivery, because every feature is on every plan. Move up when you need more events.