Gareth Wilson Gareth Wilson

Guide to DealHub Webhooks: Features and Best Practices

Published


DealHub is a CPQ (configure, price, quote) platform with DealRoom, a digital sales room where buyers review and sign quotes. Its webhooks report what happens to quotes, versions and users, and what buyers and sellers do inside a DealRoom. Instead of polling the API for quote status, your system hears from DealHub when a quote is submitted, approved, published or signed.

This guide covers how DealHub webhooks are configured and authenticated, the events and payloads they carry, the gaps in DealHub's documentation, and the best practices for running a DealHub receiver in production.

What are DealHub webhooks?

A DealHub webhook is an HTTPS endpoint that an administrator registers in DealHub and subscribes to a list of events. When one of those events happens, DealHub sends an HTTP POST with a JSON payload. The event field in the body names the event.

DealHub doesn't sign its webhooks. Instead, you choose how DealHub authenticates itself to your endpoint: a bearer token, Basic credentials, or nothing. Whatever you choose is sent in the Authorization header of every request, and your receiver checks it.

DealHub webhook features

FeatureDetails
ConfigurationAdmin mode > System Settings > API Settings > Webhooks
EndpointHTTPS only
Subscription modelA checkbox per event
Method and formatPOST with a JSON body
AuthenticationToken (Authorization: Bearer <TOKEN>), Basic (Authorization: Basic <CREDENTIALS>) or None
SignatureNone
Event identifierevent field in the body
Delivery metadataevent_info with webhook_id, event_id, api_version and, for async requests, request_id
Timestampsexecution_date in GMT, formatted yyyy-mm-dd hh:mm:ss
Custom dataAnswers to up to five playbook questions in additional_data, configured per Version
RetriesNot documented
Source IPsNot documented

Common events

Quote events

EventFires when
draftQuoteA quote is created and saved as a draft, or a draft is modified
primaryQuoteA user or the system marks a quote as the primary quote
quotePendingApprovalA quote that needs approval is submitted, and again as each approver approves their part
quoteReadyA quote is ready to share: submitted with no approval needed, or fully approved
quoteRejectedAn approver rejects the quote
quoteRecalledA user recalls (cancels) the quote
quotePublishedThe quote is published to a DealRoom
quoteUnpublishedA user unpublishes the DealRoom
quoteWonThe quote is signed, in DealRoom or externally

Two details matter when you build a state machine on these. If a user creates a quote and submits it in one motion, DealHub sends only quotePendingApproval, not draftQuote. And when Quote B becomes primary in place of Quote A, DealHub sends primaryQuote for Quote B only: "the consuming system must infer" that Quote A lost the flag.

A quoteWon payload looks like this:

{
  "event": "quoteWon",
  "status": "Won",
  "external_opportunity_id": "786865656565",
  "external_quote_id": "",
  "external_customer_id": "65545425413",
  "dealhub_opportunity_id": "2323232565685498",
  "dealhub_quote_id": "6563232565685498",
  "primary_quote": true,
  "execution_date": "2021-10-22 16:39:08",
  "executed_by": "john@mydomain.com",
  "impersonated_by": "",
  "additional_data": [
    { "q1": "some text" },
    { "q2": "red;green;blue" },
    { "q3": "" }
  ]
}

DealHub's suggested pattern is to take dealhub_quote_id from the webhook and call the Get Quote API for line items and answers, rather than expecting the payload to carry everything.

Version, user and system events

EventFires when
versionAn administrator creates, activates or reactivates a version. action is created, activated or reactivated
userA user is created, modified or deleted. action is created, modified or deleted
productsUploadAn SFTP or Open API product upload completes. upload_status is success or error
crmImportA CRM Import request (buyer accounts and contacts) completes, with total, succeeded, failed and skipped counts
Failure WebHook v1An asynchronous API request, such as duplicating or activating a version, fails

An SFTP upload loads data into every draft Version on the account, so productsUpload is "published once per affected draft Version". Expect several events for one upload. The documented Failure WebHook v1 body contains only error_description and error_code, with no event field.

DealRoom events

All DealRoom activity arrives as one event, DEALROOM_EVENT. The key inside action_summary says what happened, and its value is a readable message:

{
  "event": "DEALROOM_EVENT",
  "DealRoom_URL": "https://...",
  "Dealhub_Opportunity_id": "OPP-123",
  "dealhub_quote_id": "QUOTE-456",
  "Event_source": "DealRoom",
  "executed_by": "John Doe",
  "execution_date": "2024-02-06 16:41:29",
  "action_summary": {
    "DEAL_BOX_FILE_ADDED": "John Doe added contract_final.pdf to the DealBox"
  }
}

DealHub lists 37 keys across DealBox files, deal management (DEAL_VIEW, DEAL_SHARE, DEAL_SIGN, DEAL_CO_SIGN), document management, online redlining and revisions. Its own advice is to filter on the key and act only on business-critical ones such as DEAL_SIGN, REDLINE_FULLY_ACCEPTED and REVISION_SIGNED. Note the mixed casing: Dealhub_Opportunity_id and Impersonated_By here, dealhub_opportunity_id and impersonated_by in quote events.

Setting up DealHub webhooks

  1. As an administrator, open admin mode, go to System Settings and select the API Settings tab.
  2. In the Webhooks section, add a new webhook.
  3. Enter a Webhook Name and the HTTPS Webhook URL.
  4. Tick each event the webhook should receive.
  5. Choose the authentication type: Token, Basic or None.
  6. Save the configuration.

To include playbook answers, open Version Settings for a version, go to Advanced Settings > Open API, and pick up to five questions in Webhook data mapping. Their answers appear in additional_data for events raised in that version's context. Multi-select answers are separated by semicolons.

Securing DealHub webhooks

Choose Token or Basic and check the Authorization header on every request. In Token mode, DealHub sends:

Authorization: Bearer <TOKEN_SHARED_BY_ENDPOINT_PROVIDER>

In Basic mode, it sends Authorization: Basic <CREDENTIALS>. DealHub doesn't say how it builds CREDENTIALS. Standard HTTP Basic authentication base64-encodes username:password, and that is what the code below assumes, so confirm it against your first real request.

Compare the whole header value in constant time:

const crypto = require('crypto');
const express = require('express');

function expectedAuthorization() {
  if (process.env.DEALHUB_WEBHOOK_TOKEN) {
    return `Bearer ${process.env.DEALHUB_WEBHOOK_TOKEN}`;
  }
  const { DEALHUB_WEBHOOK_USERNAME: user, DEALHUB_WEBHOOK_PASSWORD: pass } = process.env;
  if (user && pass) {
    return `Basic ${Buffer.from(`${user}:${pass}`, 'utf8').toString('base64')}`;
  }
  return null;
}

function verifyDealhubAuthorization(header, expected) {
  if (typeof header !== 'string' || !expected) return false;
  const received = Buffer.from(header, 'utf8');
  const wanted = Buffer.from(expected, 'utf8');
  return received.length === wanted.length && crypto.timingSafeEqual(received, wanted);
}

const app = express();

app.post('/webhooks/dealhub', express.raw({ type: '*/*' }), (req, res) => {
  const expected = expectedAuthorization();
  if (!expected) return res.sendStatus(500); // fail closed

  if (!verifyDealhubAuthorization(req.headers['authorization'], expected)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString('utf8'));
  // Deduplicate on event.event_info?.event_id, route on event.event, then process asynchronously.
  res.sendStatus(200);
});

The same check in Python:

import base64
import hmac
import os


def expected_authorization():
    token = os.environ.get("DEALHUB_WEBHOOK_TOKEN")
    if token:
        return f"Bearer {token}"
    user = os.environ.get("DEALHUB_WEBHOOK_USERNAME")
    password = os.environ.get("DEALHUB_WEBHOOK_PASSWORD")
    if user and password:
        return "Basic " + base64.b64encode(f"{user}:{password}".encode()).decode()
    return None


def verify_dealhub_authorization(header, expected) -> bool:
    if not header or not expected:
        return False
    return hmac.compare_digest(header.encode(), expected.encode())

Use a long random token. A bearer token is a shared secret sent with every request, so it only protects you over HTTPS, and anyone who sees one request can replay it.

None suits an endpoint that carries its own Shared Access Signature in the URL. Otherwise it leaves the endpoint open to anyone who learns the URL.

Make DealHub webhooks production-ready. Hookdeck Event Gateway checks the Authorization header on every DealHub delivery, durably queues each quote and DealRoom event, and lets you replay any of them into your service.

DealHub webhook limitations and pain points

There is no signature

The Problem: You can confirm a request carried the right credentials, but not that DealHub sent it or that the body wasn't changed.

Why It Happens: DealHub authenticates with a static Authorization header and doesn't sign the body. The header is the same on every request.

Workarounds:

  • Use Token or Basic, never None, unless your URL carries its own signature.
  • Use a long random token and rotate it if it might have leaked.
  • Treat the webhook as a notification and fetch the quote with the Get Quote API before acting on money or signatures.

How Hookdeck Can Help: Hookdeck's DealHub source type compares the whole Authorization header value, so you enter it exactly as DealHub sends it, Bearer or Basic included. Verification is optional on the source, so leave it off if you chose None in DealHub.

Delivery guarantees aren't documented

The Problem: Your endpoint is down while a quote is signed, and you can't tell whether quoteWon will be sent again.

Why It Happens: DealHub's webhook pages describe events and payloads but give no retry schedule, response timeout or failure handling.

Workarounds:

  • Return a 2xx as soon as the event is stored.
  • Reconcile quote status with the API on a schedule, so a missed event is caught.

How Hookdeck Can Help: Hookdeck accepts each DealHub delivery as soon as it arrives and stores it, so an outage on your side doesn't lose events. You can retry or replay stored events into your service once it recovers.

The documented examples don't match the description

The Problem: You plan to deduplicate on event_info.event_id, but the field isn't in any example payload.

Why It Happens: DealHub says "all payloads include a standard event_info object", yet none of its quote, version, user or DealRoom examples show it, and the Failure WebHook v1 example has only two fields. DealRoom events also use different key casing from quote events.

Workarounds:

  • Capture a real delivery for each event you handle before relying on any field.
  • Fall back to a key built from event, the entity ID and execution_date when event_info is missing.
  • Read keys case-insensitively where DealHub's casing differs.

How Hookdeck Can Help: Hookdeck's event log shows the full body and headers of every delivery, so you can see exactly what DealHub sends before writing your handler.

One action can produce several events

The Problem: A single product upload or approval flow triggers more events than you expected.

Why It Happens: productsUpload fires once per affected draft Version, and quotePendingApproval fires again as each approver approves. DEALROOM_EVENT fires for views, downloads and comments as well as signatures.

Workarounds:

  • Group productsUpload events by event_info.request_id.
  • Use approval_details to tell approval steps apart.
  • Filter DealRoom events on the action_summary key.

How Hookdeck Can Help: Hookdeck filters can drop DealRoom activity you don't need, such as DEAL_VIEW, before it reaches your service.

Best practices

Compare the whole header in constant time

Check the full Authorization value, including the scheme, with crypto.timingSafeEqual or hmac.compare_digest, and check the lengths first in Node. Fail closed if the expected value isn't configured.

Deduplicate deliveries

Store event_info.event_id when it's present and skip events you've already handled. See our guide to webhook idempotency.

Treat optional fields as optional

DealHub omits fields with no value rather than sending empty ones. Use optional access for impersonated_by, external_quote_id, additional_data and anything else marked "can be omitted".

Parse dates as GMT

execution_date has no time zone marker. Parse it as GMT explicitly, or your timestamps will shift by your server's offset.

Acknowledge first, process after

Store the event, return 2xx, then call the Get Quote API or update your CRM. See why to process webhooks asynchronously.

Conclusion

DealHub webhooks carry quote, version, user, DealRoom and system events as JSON, authenticated by a static Authorization header rather than a signature. Choose Token or Basic, compare the whole header in constant time, capture real payloads before trusting fields the examples leave out, and reconcile with the API because DealHub doesn't document retries.

Hookdeck Event Gateway checks each DealHub delivery, stores it, and retries into your service on your schedule.

Get started with Hookdeck for free and handle DealHub webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.