Test HubSpot Webhooks Locally (Receive HubSpot Webhooks on localhost)

Test HubSpot webhooks locally and receive them on localhost without deploying. Inspect the batched payload, forward to your handler, and verify the X-HubSpot-Signature-v3 signature.

Test HubSpot Webhooks Locally (Receive HubSpot Webhooks on localhost)

You are building a HubSpot integration — syncing CRM contacts, reacting to a deal stage change, kicking off an automation — and you need to watch your handler react to a real contact.creation or deal.propertyChange event. The problem is immediate: HubSpot will only POST to a public URL, and your handler is running on localhost:3000. HubSpot has no way to reach it.

The usual workarounds are painful. Deploying to a staging server for every change is slow. Copying a sample payload from the docs into curl gives you a guess at the real request — not the real headers, the real signature, or HubSpot's batched array format. What you want is to test HubSpot webhooks locally — real events hitting your local handler, with a URL that doesn't change every time you restart.

This guide shows how to do exactly that.

Why receiving HubSpot webhooks locally is tricky

A webhook is just an HTTP request that HubSpot sends to a URL when something happens in your CRM. HubSpot sits on the public internet; your dev machine usually does not. It's behind a router, a corporate firewall, or both, with no public IP and no inbound ports open.

So you need something in the middle: a public endpoint HubSpot can hit, that relays each request down to your laptop without you opening a single firewall port. That's what Webhook Relay does — and unlike a random tunnel URL, the endpoint is stable, so you configure HubSpot once and never touch it again.

Step 1: Inspect the real payload with Webhook Bin

Before you write any handler code, find out what HubSpot actually sends. Open the free Webhook Bin — no signup — and you get an instant public URL.

  1. Copy the Webhook Bin URL.
  2. In HubSpot, open your app's Webhooks settings (see Step 2 for exactly where) and set this URL as the Target URL.
  3. Subscribe to an event such as contact.creation, then create a contact in your portal.

Inspect the captured request and you'll notice two HubSpot-specific things right away:

  • The body is a JSON array. HubSpot batches events, so even a single event arrives as an array of event objects. Your handler must loop over the array.
  • The signature and timestamp headers. Newer apps send X-HubSpot-Signature-v3 together with X-HubSpot-Request-Timestamp. You'll verify these in Step 3.

Now you know the exact shape of the data before writing a line of code. For more on this approach, see How to test webhooks and What is a webhook.

Step 2: Forward the events to localhost with the relay agent

Once you know the payload, route those same events into your local handler. Sign up for Webhook Relay, install the relay agent (CLI or Docker), and create a bucket — say hubspot. The bucket gives you a stable public input endpoint.

Start forwarding to your local server:

relay forward --bucket hubspot http://localhost:3000/webhook

The agent opens an outbound connection to Webhook Relay and streams every incoming request down to http://localhost:3000/webhook. Because the connection is outbound, there are no firewall ports to open and no public IP needed — this works from your laptop, behind a corporate proxy, or inside a Kubernetes cluster. Running in Docker? The same command works in the official webhookrelay/webhookrelayd image. Full details are in the localhost forwarding docs.

Now update the HubSpot Target URL to your Webhook Relay endpoint (or just set it there from the start), trigger an event, and watch it arrive on localhost.

HubSpot-specific configuration and quirks

Webhooks live inside an app, not in the general portal settings:

  • Where to configure them: create or open a private app (or a developer/public app) in HubSpot, then go to App settings → Webhooks. Set the Target URL to your Webhook Relay endpoint and add subscriptions for the events you care about — contact.creation, contact.propertyChange, deal.creation, deal.propertyChange, and so on across CRM objects.
  • You need the right scopes. Webhook subscriptions require the relevant CRM scopes (for example, contacts) to be granted to the app.
  • Events are batched. As noted above, the body is always a JSON array. Each element carries fields like subscriptionType, objectId, propertyName, propertyValue, occurredAt, and eventId. Iterate; don't assume one object.
  • Concurrency and retries. HubSpot may deliver batches concurrently and retries failed deliveries, so make your handler idempotent — dedupe on eventId — and return a 2xx quickly.

Step 3: Verify the HubSpot webhook signature

Authenticate every request before trusting it. Newer HubSpot apps use the X-HubSpot-Signature-v3 header, which is the most secure option and the only one with replay protection. Here's how v3 works:

  1. Check the timestamp. Read X-HubSpot-Request-Timestamp. HubSpot timestamps are in milliseconds. Reject the request if it's more than 5 minutes old — this is what stops replay attacks.
  2. Build the signed string. Concatenate, in order: the HTTP method + the full request URI + the raw request body + the timestamp. Decode any URL-encoded characters in the URI (but leave the ? that begins the query string).
  3. Compute the HMAC. Take an HMAC-SHA256 of that string using your app's client secret as the key.
  4. Base64-encode and compare. Base64-encode the HMAC result and compare it to the X-HubSpot-Signature-v3 header using a constant-time comparison.

A subtle but critical detail: the signed string includes the full URL HubSpot actually called — which, in this setup, is your Webhook Relay endpoint, not localhost. Compute and capture the v3 signature against the public URL HubSpot saw, exactly as it arrived in the forwarded request.

Older apps may still send the v1 (X-HubSpot-Signature as a SHA-256 over client-secret + body) or v2 schemes; v3 is the current, recommended version. To sanity-check an HMAC-SHA256 implementation quickly, paste a captured body, your secret, and the received signature into the free HMAC signature verifier. For language-specific code and the common pitfalls — reading the body after a JSON parser has consumed it, timing-safe comparison — read Verify a webhook signature.

Replay and iterate

This is where local development gets fast:

  • Replay from Webhook Relay — past requests are stored on your bucket, so you can resend a captured HubSpot batch against your handler without touching HubSpot at all.
  • Iterate on your handler by editing code and replaying the same delivery until it behaves correctly. No object changes in the CRM, no deploys, just to test a code path.

Because the Webhook Relay endpoint is stable, you can stop and restart the agent, reboot your machine, or come back next week — the HubSpot Target URL never needs to change.

Get started

  1. Inspect the real payload in the free Webhook Bin — no signup needed.
  2. Create a Webhook Relay account, install the agent, and run relay forward --bucket hubspot http://localhost:3000/webhook.
  3. Point your HubSpot private app's Target URL at the stable endpoint, trigger an event, and watch it hit localhost.

You'll be testing real HubSpot events against your local handler in a few minutes — no deploys, no open firewall ports, and a URL you configure exactly once.