BlogHubSpot Webhooks

How to Test HubSpot Webhooks

HubSpot webhooks notify your app when CRM records like contacts, companies, and deals are created, updated, or deleted.

Looking for the broader picture? See the 7 best webhook testing tools (2026), or if you're already on Webhook.site, the 60-second migration to HookRay.

HubSpot Official Webhook Docs

1. HubSpot Webhook Events

HubSpot can send the following webhook events to your endpoint:

contact.creation
contact.propertyChange
contact.deletion
company.creation
company.propertyChange
company.deletion
deal.creation
deal.propertyChange
deal.deletion

2. Set Up a Test Endpoint with HookRay

Follow these steps to start receiving HubSpot webhooks for testing:

  1. Go to HookRay and click "Start Testing — Free" to get your unique webhook URL.
  2. Copy the URL (e.g., https://h.hookray.com/abc123).
  3. In your HubSpot dashboard, navigate to the webhook settings and paste the HookRay URL as your endpoint.
  4. Select the events you want to receive (see list above).
  5. Trigger a test event — HookRay will show the incoming webhook in real-time.

3. Sample HubSpot Webhook Payload

Here's an example of what a HubSpot webhook payload looks like:

payload.json
{
  "eventId": 18273645,
  "subscriptionType": "deal.propertyChange",
  "portalId": 1234567,
  "occurredAt": 1763582400000,
  "objectId": 987654321,
  "propertyName": "dealstage",
  "propertyValue": "closedwon"
}

4. How to Verify HubSpot Webhook Signatures

Signing details
Algorithm
HMAC-SHA256
Header
X-HubSpot-Signature-v3
Encoding
base64

HubSpot v3 signs the concatenation of `{HTTP_METHOD}{request_URI}{raw_request_body}{X-HubSpot-Request-Timestamp}` with your app's Client Secret. URL-encoded characters in the URI must be decoded before signing (except the `?` that begins the query string). Reject requests older than 5 minutes.

Node.js (Express)

import crypto from 'node:crypto';
import express from 'express';

const app = express();

app.post(
  '/webhooks/hubspot',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['x-hubspot-signature-v3'] as string | undefined;
    const timestamp = req.headers['x-hubspot-request-timestamp'] as string | undefined;
    if (!signature || !timestamp) return res.status(401).send('missing headers');

    // Replay protection — reject requests older than 5 minutes.
    const fiveMinutes = 1000 * 60 * 5;
    if (Math.abs(Date.now() - Number(timestamp)) > fiveMinutes) {
      return res.status(401).send('stale request');
    }

    // Reconstruct the URI HubSpot signed (decode percent-encoded chars).
    const uri = decodeURIComponent(req.originalUrl);
    const stringToSign =
      req.method + 'https://' + req.headers.host + uri + req.body.toString('utf8') + timestamp;

    const expected = crypto
      .createHmac('sha256', process.env.HUBSPOT_CLIENT_SECRET!)
      .update(stringToSign)
      .digest('base64');

    if (
      signature.length !== expected.length ||
      !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
    ) {
      return res.status(403).send('invalid signature');
    }
    res.json({ ok: true });
  },
);

Python (FastAPI)

import hmac, hashlib, base64, os, time
from urllib.parse import unquote
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()

@app.post("/webhooks/hubspot")
async def hubspot_webhook(request: Request):
    body = await request.body()
    signature = request.headers.get('x-hubspot-signature-v3', '')
    timestamp = request.headers.get('x-hubspot-request-timestamp', '0')

    if abs(int(time.time() * 1000) - int(timestamp)) > 1000 * 60 * 5:
        raise HTTPException(status_code=401, detail='stale request')

    uri = unquote(str(request.url))
    string_to_sign = (request.method + uri + body.decode() + timestamp).encode()
    expected = base64.b64encode(
        hmac.new(
            os.environ['HUBSPOT_CLIENT_SECRET'].encode(),
            msg=string_to_sign,
            digestmod=hashlib.sha256,
        ).digest()
    ).decode()
    if not hmac.compare_digest(signature, expected):
        raise HTTPException(status_code=403, detail='invalid signature')
    return {'ok': True}
Watch out: HubSpot v3 includes the HTTP method and full URI in the signed string — proxies that change the host or path break verification. Use the Client Secret of your app, not your private app's API key. v1 and v2 algorithms exist but are legacy; new integrations must use v3.

Capture a real HubSpot webhook with HookRay first, then replay the captured request against your verifier locally — that way you can iterate on the verification code without re-triggering events in HubSpot. Read HubSpot's official signing docs for the canonical reference, or see the cross-service signature verification guide for Ruby and timing-safe comparison patterns.

5. How HubSpot Retries Failed Webhooks

Retry policy
Max attempts
10
Total window
Up to ~8 hours
Backoff
Exponential, starts after a few minutes
Retries on
5xx, 429, timeouts (5s)
Stops on
Any 2xx response within 5s
Watch out: HubSpot batches multiple events in a single POST (an array of event objects). Your idempotency key must be the per-event `eventId` inside the array, NOT a hash of the request body — otherwise retries of the same batch all dedupe to one event.

If your handler is going to see the same HubSpot event multiple times, you need idempotency by event ID and the right response codes — see the Webhook Retry Strategies guide for the full pattern (idempotency tables, dead-letter queues, replaying captured events for tests). Cross-reference with HubSpot's official retry docs before relying on these numbers in production.

6. Frequently Asked Questions

How do I test HubSpot webhooks without deploying?

Use HookRay to get an instant public webhook URL. Paste it into your HubSpot dashboard's webhook configuration, trigger an event, and watch the payload arrive in real time. No code, no ngrok, no deployment required. The free tier captures 100 requests per month and works on all HubSpot event types.

Why aren't my HubSpot webhooks arriving?

The four most common causes: (1) the endpoint URL isn't publicly accessible — HubSpot can't reach localhost; (2) the wrong events are subscribed in your HubSpot dashboard; (3) signature verification is rejecting the request before your handler runs; (4) HubSpot can't reach your server because of a firewall, expired SSL certificate, or wrong DNS. Use HookRay's URL to isolate which of these four is failing — if HookRay receives the webhook, the problem is in your handler. If HookRay doesn't, the problem is in HubSpot configuration.

Why am I getting 400 or 500 errors from my HubSpot webhook?

HubSpot reports the response status your endpoint returned. HookRay accepts any payload and returns 200 OK by default, so if you see 400/500 in your HubSpot dashboard while pointing at HookRay, the issue is in HubSpot's configuration (wrong event, malformed signing secret, etc.). If you point at your own endpoint and get 400/500, the issue is in your handler — capture the request with HookRay, replay it locally, and debug from the captured payload.

How do I verify HubSpot webhook signatures?

HubSpot signs each webhook request with a shared secret. Capture the raw headers and body using HookRay, then verify the signature in your application using HubSpot's SDK or a standard HMAC library. Once verification works against HookRay-captured data, you can safely deploy. HubSpot's docs (linked above) cover the exact signing algorithm.

Can I replay a captured HubSpot webhook?

Yes — HookRay's replay feature re-sends any captured webhook to a different endpoint with one click. This is the fastest way to fix a buggy handler: capture the payload once, fix your code, and replay until it works. No need to re-trigger the event in HubSpot.

7. Next Steps

  • Use HookRay's webhook replay feature to re-send captured webhooks while building your handler
  • Enable smart parsing (Pro plan) to see HubSpot-specific fields highlighted automatically
  • Check the HubSpot webhook documentation for the complete event reference

Ready to test HubSpot webhooks?

Get a free webhook URL in 5 seconds. No signup required.

Start Testing HubSpot Webhooks — Free

Free PDF: Webhook Testing Cheat Sheet 2026

One-page reference for 50+ APIs — canonical events, signing methods, sample payloads. Print it, pin it, share it.

📄 Download the cheat sheet (PDF, 180KB)