Agent skill

QuickBooks Webhooks Skill

Receive and verify QuickBooks Online (Intuit) webhooks. Use when setting up QuickBooks webhook handlers, debugging intuit-signature verification with the Verifier Token, migrating from the legacy eventNotifications payload to the CloudEvents format, or handling accounting events like qbo.invoice.created.v1, qbo.customer.updated.v1, and qbo.customer.merged.v1.

Install this skill

npx skills add hookdeck/webhook-skills --skill quickbooks-webhooks


When to Use This Skill

  • How do I receive QuickBooks Online webhooks?
  • How do I verify the intuit-signature header with my Verifier Token?
  • Why is my QuickBooks webhook signature verification failing?
  • How do I handle the new CloudEvents payload (qbo.invoice.created.v1) and the legacy eventNotifications payload?
  • How do I process QuickBooks webhooks for multiple companies (realm IDs) in one notification?

Verification (core)

Intuit signs the raw request body with HMAC-SHA256 keyed on your app's Verifier Token, base64-encodes the digest, and sends it in the intuit-signature header. There is no timestamp in the signed content. Read the raw body before JSON parsing and compare timing-safe. Intuit's SDKs have no webhook-verification helper, so verify manually.

Node:

const crypto = require('crypto');

function verifyQuickBooksSignature(rawBody, signature, verifierToken) {
  if (!signature) return false;
  const expected = crypto.createHmac('sha256', verifierToken).update(rawBody).digest('base64');
  try {
    return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  } catch {
    return false; // length mismatch = invalid
  }
}

Python:

import hmac, hashlib, base64

def verify_quickbooks_signature(raw_body: bytes, signature: str, verifier_token: str) -> bool:
    if not signature:
        return False
    expected = base64.b64encode(hmac.new(verifier_token.encode(), raw_body, hashlib.sha256).digest())
    return hmac.compare_digest(signature.encode(), expected)

Respond with 200 within 3 seconds. Intuit retries failures (10s, 20s, 30s, 5m, 20m, 2h, 4h, 6h, then every 6h) and may hold back later events until the first one is acknowledged. Queue the work and process it asynchronously.

For complete handlers with route wiring, event dispatch, and tests, see:

Payload Structure

QuickBooks webhooks use the CloudEvents format: a JSON array of events, which can cover several companies (realm IDs) in one delivery. Payloads are thin. Fetch the full record from the Accounting API using intuitentityid and intuitaccountid.

[
  {
    "specversion": "1.0",
    "id": "88cd52aa-33b6-4351-9aa4-47572edbd068",
    "source": "intuit.dsnBgbseACLLRZNxo2dfc4evmEJdxde58xeeYcZliOU=",
    "type": "qbo.invoice.created.v1",
    "datacontenttype": "application/json",
    "time": "2026-09-10T21:31:25Z",
    "intuitentityid": "1234",
    "intuitaccountid": "310687",
    "data": {}
  }
]

Apps built before the 2026 migration got the legacy object shape {"eventNotifications":[{"realmId", "dataChangeEvent":{"entities":[{"name","id","operation","lastUpdated"}]}}]}. The examples normalize both shapes into one event list, and the same signature check covers both.

Common Event Types

type is qbo.{entitytype}.{event}.v1, with the entity name in lowercase.

Event typeTriggered When
qbo.invoice.created.v1An invoice is created
qbo.invoice.updated.v1An invoice is changed
qbo.payment.created.v1A customer payment is recorded
qbo.customer.created.v1A customer is created
qbo.customer.updated.v1A customer is changed
qbo.customer.merged.v1Two customers are merged (data.deletedid holds the removed ID)
qbo.salesreceipt.updated.v1A sales receipt is changed
qbo.employee.created.v1An employee is created

Supported operations per entity are Create, Update, Delete, Merge, Void and Emailed, depending on the entity (see overview.md). The examples dispatch on the {event} segment (created, updated, deleted, merged, voided, emailed) so every entity is covered.

Environment Variables

QUICKBOOKS_WEBHOOK_VERIFIER_TOKEN=your_verifier_token   # Developer dashboard > your app > Webhooks > Verifier Token

Development (sandbox) and Production each have their own Verifier Token. Use the one that matches the environment that sends to the endpoint.

Local Development

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

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Oct 28, 2026

View on GitHub →