Agent skill

INGENIOUS.BUILD Webhooks Skill

Receive and authenticate INGENIOUS.BUILD Custom Accounting Integration webhooks. Use when setting up an INGENIOUS.BUILD webhook endpoint, checking the auth header you add to each webhook URL, debugging 401s, or handling invoice_exported_to_accounting, contract_exported_to_accounting and budget_exported_to_accounting events.

Install this skill

npx skills add hookdeck/webhook-skills --skill ingenious-build-webhooks


INGENIOUS.BUILD (construction project management) pushes approved financial records to your accounting middleware through the Custom Accounting Integration. You configure one webhook URL per object type (Budgets, Budget Changes, Contracts, Contract Changes, Invoices). A webhook fires only when a user clicks Sync to Accounting on an approved record.

Requests are not signed. You authenticate them with a secret header that you add to each webhook URL in the integration settings, and your endpoint compares that header with the value it has stored.

Setup note (same as Hookdeck's INGENIOUS.BUILD source): "Add an auth header, such as Authorization: Bearer <token>, to each webhook URL on the Custom Accounting Integration."

When to Use This Skill

  • How do I receive INGENIOUS.BUILD webhooks?
  • How do I authenticate INGENIOUS.BUILD Custom Accounting Integration requests?
  • Does INGENIOUS.BUILD sign webhook payloads? (No: use the auth header you configure)
  • How do I handle invoice_exported_to_accounting or contract_exported_to_accounting?
  • Why does my INGENIOUS.BUILD webhook endpoint return 401?

Verification (core)

const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyIngeniousBuildRequest(
  headers,
  name = process.env.INGENIOUS_BUILD_WEBHOOK_HEADER || 'Authorization',
  secret = process.env.INGENIOUS_BUILD_WEBHOOK_SECRET
) {
  if (!secret) return false;                        // never accept when unconfigured
  const received = headers[name.toLowerCase()];     // Node lowercases header names
  return Boolean(received) && safeEqual(received, secret); // WHOLE value, no 'Bearer ' parsing
}
  • The header name is whatever you typed in the integration (Authorization by default here). Look it up case-insensitively and compare the whole value to INGENIOUS_BUILD_WEBHOOK_SECRET, including any Bearer prefix you typed. Do not strip or parse the prefix.
  • Return 401 for a missing header or a mismatch, 400 for a body that isn't a JSON object, 200 once handled.
  • Read the body raw (express.raw({ type: '*/*' })) so any content type works, and parse it only after the header check.
  • A static shared credential authenticates the sender, not the body, and gives no replay protection (there is no signature and no timestamp). Use HTTPS only and rotate the credential if it leaks.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Event Types

Every request is a POST with a JSON body { name, subject, data }. Dispatch on name.

namesubjectSent When
budget_exported_to_accountingbudgetSync to Accounting is clicked on a Budget
budget_change_exported_to_accountingbudget_change... on a Budget Change
contract_exported_to_accountingcontract... on a Contract
contract_change_exported_to_accountingcontract_change... on a Contract Change
invoice_exported_to_accountinginvoice... on an Invoice

Environment Variables

INGENIOUS_BUILD_WEBHOOK_HEADER=Authorization       # header name you added to each webhook URL
INGENIOUS_BUILD_WEBHOOK_SECRET="Bearer your-token" # exact, whole header value

Local Development

INGENIOUS.BUILD can't reach localhost. Tunnel with the Hookdeck CLI (no account required):

npx hookdeck-cli listen 3000 ingenious-build --path /webhooks/ingenious-build

Use the printed URL as a webhook URL in the Custom Accounting Integration.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 6, 2026

View on GitHub →