# HubSpot Webhooks

## When to Use This Skill

* Setting up HubSpot webhook handlers
* Verifying `X-HubSpot-Signature-v3` headers
* Debugging signature verification failures
* Handling CRM events like contact creation, property changes, or deal events
* Migrating from HubSpot signature v1/v2 to v3

## Essential Code (USE THIS)

HubSpot does not provide an SDK helper for webhook signature verification, so verification is implemented manually with HMAC-SHA256 and base64 across all frameworks.

### HubSpot Signature Verification (JavaScript)

```javascript
const crypto = require('crypto');

const MAX_AGE_MS = 5 * 60 * 1000; // 5 minutes

/**
 * Verify HubSpot v3 webhook signature.
 *
 * Signed content = HTTP method + request URI + raw body + timestamp
 * Signature is HMAC-SHA256 (base64) of that string using the app's Client Secret.
 */
function verifyHubSpotWebhook({ method, uri, rawBody, timestamp, signature, secret }) {
  if (!signature || !timestamp || !secret) return false;

  // Reject stale requests (older than 5 minutes)
  const ts = Number(timestamp);
  if (!Number.isFinite(ts) || Math.abs(Date.now() - ts) > MAX_AGE_MS) return false;

  const body = Buffer.isBuffer(rawBody) ? rawBody.toString('utf8') : rawBody;
  const signedContent = `${method}${uri}${body}${timestamp}`;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(signedContent, 'utf8')
    .digest('base64');

  try {
    return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
  } catch {
    return false;
  }
}

```

### Express Webhook Handler

```javascript
const express = require('express');
const app = express();

// CRITICAL: Use express.raw() - HubSpot requires raw body for HMAC verification
app.post('/webhooks/hubspot',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['x-hubspot-signature-v3'];
    const timestamp = req.headers['x-hubspot-request-timestamp'];

    // Reconstruct the full request URI (HubSpot signs the URL it called)
    const uri = `${req.protocol}://${req.get('host')}${req.originalUrl}`;

    const valid = verifyHubSpotWebhook({
      method: req.method,
      uri,
      rawBody: req.body,
      timestamp,
      signature,
      secret: process.env.HUBSPOT_CLIENT_SECRET,
    });

    if (!valid) {
      console.error('HubSpot signature verification failed');
      return res.status(400).send('Invalid signature');
    }

    // HubSpot sends an array of events in each webhook
    const events = JSON.parse(req.body.toString());

    for (const event of events) {
      switch (event.subscriptionType) {
        case 'contact.creation':
          console.log('New contact:', event.objectId);
          break;
        case 'contact.propertyChange':
          console.log('Contact property changed:', event.objectId, event.propertyName);
          break;
        case 'deal.creation':
          console.log('New deal:', event.objectId);
          break;
        default:
          console.log('Unhandled event:', event.subscriptionType);
      }
    }

    res.status(200).send('OK');
  }
);

```

### Python (FastAPI) Signature Verification

```python
import hmac
import hashlib
import base64
import time

MAX_AGE_MS = 5 * 60 * 1000  # 5 minutes

def verify_hubspot_webhook(method: str, uri: str, raw_body: bytes,
                           timestamp: str, signature: str, secret: str) -> bool:
    if not signature or not timestamp or not secret:
        return False

    try:
        ts = int(timestamp)
    except ValueError:
        return False

    if abs(int(time.time() * 1000) - ts) > MAX_AGE_MS:
        return False

    body = raw_body.decode("utf-8")
    signed_content = f"{method}{uri}{body}{timestamp}"

    expected = base64.b64encode(
        hmac.new(secret.encode("utf-8"), signed_content.encode("utf-8"), hashlib.sha256).digest()
    ).decode("utf-8")

    return hmac.compare_digest(expected, signature)

```

> For complete working examples with tests, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/hubspot-webhooks/examples/express/) - Full Express implementation
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/hubspot-webhooks/examples/nextjs/) - Next.js App Router implementation
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/hubspot-webhooks/examples/fastapi/) - Python FastAPI implementation

## Common Event Types

HubSpot calls these `subscriptionType` values. Each webhook delivery contains an array of one or more event objects.

| Event | Description |
| --- | --- |
| `contact.creation` | A new contact was created |
| `contact.propertyChange` | A property on a contact changed |
| `contact.deletion` | A contact was deleted |
| `company.creation` | A new company was created |
| `company.propertyChange` | A property on a company changed |
| `deal.creation` | A new deal was created |
| `deal.propertyChange` | A property on a deal changed |
| `ticket.creation` | A new ticket was created |

> For full event reference, see [HubSpot Webhooks API](https://developers.hubspot.com/docs/api/webhooks).

## Environment Variables

```bash
HUBSPOT_CLIENT_SECRET=your_app_client_secret   # From your HubSpot app settings

```

The signing key is your App's Client Secret (sometimes called Application Secret), not a private app token.

## Signature Versions

HubSpot has shipped three signature versions:

* v1 (`X-HubSpot-Signature`) - SHA-256 of `clientSecret + body`. Deprecated.
* v2 (`X-HubSpot-Signature`, `X-HubSpot-Signature-Version: v2`) - SHA-256 of `clientSecret + method + URI + body`. Deprecated.
* v3 (`X-HubSpot-Signature-v3`, requires `X-HubSpot-Request-Timestamp`) - HMAC-SHA256 (base64) of `method + URI + body + timestamp`. Use this.

New integrations should use v3 only. A v4 webhooks API is in beta on HubSpot's new developer platform but uses different mechanics; pin to v3 for stability.

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 hubspot --path /webhooks/hubspot

```

Then paste the Hookdeck URL into your HubSpot app's webhook settings as the target URL.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hubspot-webhooks/references/overview.md) - HubSpot webhook concepts and event types
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hubspot-webhooks/references/setup.md) - Configure webhooks in the HubSpot app dashboard
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hubspot-webhooks/references/verification.md) - Signature verification details and gotchas