Agent skill

Procore Webhooks Skill

Receive and authenticate Procore construction webhooks. Use when setting up a Procore company or project webhook (hook) endpoint, checking the Authorization Header credential, debugging 401s, or handling resource create, update and delete events such as RFIs or submittals.

Install this skill

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


Procore (construction management platform) lets company and project admins create hooks (Company Admin > Webhooks) that POST a notification to your endpoint whenever a Procore API resource is created, updated or deleted. Payloads are not signed. The only credential is the optional Authorization Header field, which Procore places verbatim in the Authorization header of each POST.

When to Use This Skill

  • How do I receive Procore webhooks?
  • How do I verify the Procore Authorization header on a webhook?
  • Does Procore sign webhook payloads? (No: shared header credential only)
  • Why does my Procore webhook endpoint return 401?
  • How do I handle Procore create, update and delete events for a resource like RFIs?

Verification (core)

Set the Authorization Header on the hook, store the exact same string as PROCORE_AUTHORIZATION, and compare it to the whole Authorization header. Paste the Authorization Header value exactly as set in Procore, including any "Bearer " or "Basic " prefix. Never strip, parse or base64-decode it: the docs' example, Basic c3a24b8208ac5199d083d54a1234e94b8864, is a token, not base64 user:pass.

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 verifyProcoreRequest(headers, expected = process.env.PROCORE_AUTHORIZATION) {
  if (!expected) return false;               // never accept when unconfigured
  const received = headers['authorization']; // full value, e.g. "Basic c3a2..."
  return Boolean(received) && safeEqual(received, expected);
}

Return 401 when the header is missing or does not match, 400 for a body that isn't a JSON object, and 200 once handled. Read the body raw (express.raw({ type: '*/*' })) so any content type works, and parse it only after the credential check. Python and Next.js equivalents are in references/verification.md.

What this does and does not give you: a static shared credential authenticates the sender, not the body, and offers no replay protection (no signature, no timestamp). Use HTTPS, and rotate the credential in Procore and in your environment if it leaks.

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

Event Types

Each event has a resource name (the Procore API resource, e.g. RFIs) and an event type:

Event typeTriggered when
createA resource record is created
updateA resource record is updated
deleteA resource record is deleted

The v2 payload is described as carrying fields like id, ulid, timestamp, user_id, company_id, project_id, api_version, event_type, resource_name and resource_id. The Procore support article confirms only the resource name, resource ID, event type and timestamp; the developer docs are not readable here, so treat the payload in the examples as illustrative.

Environment Variables

PROCORE_AUTHORIZATION=Basic c3a24b8208ac5199d083d54a1234e94b8864   # full header value

Local Development

npx hookdeck-cli listen 3000 procore --path /webhooks/procore

No account required. Hookdeck's Procore source shows this note: Paste the Authorization Header value exactly as set in Procore, including any "Bearer " or "Basic " prefix. Give the printed URL to Procore as the hook's Endpoint URL.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 9, 2026

View on GitHub →