# DealHub Webhooks

DealHub is a CPQ and quote-to-revenue platform. It sends an HTTP POST with a
JSON body to your HTTPS endpoint when quotes, versions, users and DealRooms
change.

## When to Use This Skill

* How do I receive DealHub webhooks?
* How do I verify / authenticate a DealHub webhook?
* Why is my DealHub webhook Authorization check failing?
* How do I handle `draftQuote`, `primaryQuote` or `quoteWon`?
* How do I tell `version` / `user` events apart (the `action` field)?
* What does a `productsUpload` or `DEALROOM_EVENT` webhook look like?

## No Signature: Authorization Header Only

DealHub **does not sign webhook payloads**. There is no signature header, no
HMAC and no timestamp. Each webhook is configured with one of three
**Authentication Types** ([How to Configure Webhooks](https://developers.dealhub.io/docs/how-to-configure-webhooks)):

| Type | What DealHub sends |
| --- | --- |
| **Token** | `Authorization: Bearer <TOKEN>` |
| **Basic** | `Authorization: Basic <CREDENTIALS>` |
| **None** | No header. Only for endpoints without auth, or with a SAS token embedded in the URL |

**Caveat:** the credential is the **full** `Authorization` header value,
scheme included. Build the exact string you expect (`Bearer <TOKEN>` or
`Basic base64(username:password)`) and compare the whole header in constant
time. Don't strip the scheme and compare only the token.

## Verification (core)

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

// Token mode: DealHub sends "Bearer <TOKEN>". Basic mode: "Basic <CREDENTIALS>".
const expected = process.env.DEALHUB_WEBHOOK_TOKEN
  ? `Bearer ${process.env.DEALHUB_WEBHOOK_TOKEN}`
  : `Basic ${Buffer.from(
      `${process.env.DEALHUB_WEBHOOK_USERNAME}:${process.env.DEALHUB_WEBHOOK_PASSWORD}`
    ).toString('base64')}`;

function verifyDealhubAuthorization(header, expected) {
  if (!header || !expected) return false;               // fail closed
  const a = Buffer.from(header, 'utf8');                 // the WHOLE header value
  const b = Buffer.from(expected, 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b); // length guard first
}

```

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/dealhub-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/dealhub-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/dealhub-webhooks/examples/fastapi/).

DealHub has no SDK with a webhook verification helper, so every example does
this compare directly (`crypto.timingSafeEqual` in Node, `hmac.compare_digest`
in Python). Authenticate before you parse the JSON body.

## Common Event Types

The event name is the payload's `event` field.

| Event | Triggered when |
| --- | --- |
| `draftQuote` | A quote is created and saved as a draft, or a draft is modified |
| `primaryQuote` | A quote is marked Primary (no event is sent for the quote that lost the flag) |
| `quotePendingApproval` | A quote is submitted for approval, and as each approver approves |
| `quoteReady` | A quote is ready to share with the customer |
| `quoteRejected` | An approver rejects the quote |
| `quoteRecalled` | A user recalls (cancels) the quote |
| `quotePublished` / `quoteUnpublished` | The quote is published to / unpublished from a DealRoom |
| `quoteWon` | The quote is signed |
| `version` | `action`: `created`, `activated`, `reactivated` |
| `user` | `action`: `created`, `modified`, `deleted` |
| `productsUpload` | An SFTP or Open API product upload completes (`upload_status`: `success` / `error`) |
| `crmImport` | A CRM Import request completes |
| `DEALROOM_EVENT` | DealRoom activity; the key of `action_summary` (e.g. `DEAL_SIGN`) names it |
| `Failure WebHook v1` | An async API request fails (documented payload has only `error_code` / `error_description`) |

Every payload includes an `event_info` object (`webhook_id`, `event_id`,
`api_version`, and `request_id` for async requests). Use
`event_info.event_id` as your idempotency key. See
[references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/dealhub-webhooks/references/overview.md) for payloads.

## Environment Variables

```bash
# Token mode: the token you entered in DealHub (sent as "Bearer <TOKEN>")
DEALHUB_WEBHOOK_TOKEN=your_shared_secret_token

# Basic mode: the username and password you entered in DealHub
DEALHUB_WEBHOOK_USERNAME=
DEALHUB_WEBHOOK_PASSWORD=

```

Set one mode. With neither set, the examples reject every request with a 500
rather than accept unauthenticated webhooks.

## Local Development

```bash
npx hookdeck-cli listen 3000 dealhub --path /webhooks/dealhub

```

No account required — the CLI creates a guest account on first run and gives
you a public HTTPS URL plus a web UI for inspecting requests (including the
`Authorization` header). Use port `8000` for the FastAPI example.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/dealhub-webhooks/references/overview.md) — Events, `action` fields, payload structure, `event_info`
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/dealhub-webhooks/references/setup.md) — Configuring webhooks in DealHub admin, choosing an Authentication Type
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/dealhub-webhooks/references/verification.md) — The Authorization header in detail, the full-value caveat, debugging failures