Guide to Procore Webhooks: Features and Best Practices
Procore webhooks tell your systems when construction project data changes. When an RFI, submittal or another Procore record is created, updated or deleted, a hook can send a notification to your service, so you can keep an ERP, document system or reporting database in step with Procore without polling its API.
This guide covers how Procore hooks are configured, how to authenticate deliveries with the Authorization Header value, what Procore tells you about each event, and the practices that make a Procore receiver reliable.
What are Procore webhooks?
In Procore, a webhook is called a hook. Company and project administrators create hooks that subscribe "to event notifications for one or more Procore API resources when Create, Update, or Delete actions occur". Procore's example is a third-party RFI integration that wants to know whenever a new RFI is created. When a subscribed event happens, Procore sends a POST to the hook's endpoint URL.
Each hook has a Payload Format, the REST API payload version Procore sends. You pick it when you create the hook, and you can't change it afterwards: to switch versions, you delete the hook and create a new one.
Procore doesn't sign webhook requests. The hook has an optional Authorization Header field, and Procore places that value in the Authorization header of every POST. Comparing the header with the value you set is how you tell a real Procore request from a forged one.
Procore webhook features
| Feature | Details |
|---|---|
| Configuration | Company Admin > Webhooks, or Project Admin > Webhooks, then Create Hook |
| Permissions | Admin on the company or project Admin tool |
| Method | POST |
| Events | Create, Update and Delete on the Procore API resources you select |
| Payload Format | The REST API payload version. Fixed once the hook is created |
| Authentication | Optional Authorization Header, sent verbatim as Authorization |
| Signature and timestamp | None |
| Event history | A rolling 28 days per hook |
| Event statuses | Delivered, Discarded, Failed, Queued, Retrying |
| Retry schedule, timeout, source IPs | Not documented in the admin guides |
Events and payloads
You choose the events in the hook's Select the Events to Send card: expand a tool, then tick the create, update or delete events for the resources you want. Selected Events shows what the hook will send.
Procore's admin guides identify each delivered event by its Resource Name, Resource ID, Event Type and a UTC timestamp. The exact field names on the wire depend on the Payload Format you chose, and Procore documents them on its developer portal, in the "Introduction to Webhooks" and "Webhooks Payload Formats" guides. Capture a real delivery for your payload version before you write the parser.
Treat the event as a pointer to the record. If you need more than the identifiers your payload version carries, fetch the resource from the Procore REST API by its ID.
Inspect Procore webhooks as they arrive. Point a hook at a Hookdeck Inspect URL to see the exact headers and body for your payload version, with no account or setup required.
Setting up Procore webhooks
Generate a long random token first, for example with openssl rand -hex 32, and decide the full value you want Procore to send, such as Bearer <token>.
- Open the company level Admin tool, and under Company Settings click Webhooks. For a single project, use the project level Admin tool and Project Settings > Webhooks instead.
- Click Create Hook.
- Enter your HTTPS endpoint in Endpoint URL.
- Enter the full value in Authorization Header, for example
Bearer <token>. - Choose the Payload Format. Agree it with whoever writes the receiver, because it can't be changed later.
- In Select the Events to Send, tick the events you need, then check Selected Events.
To route through Hookdeck, create a source with the Procore type and use its URL as the endpoint URL. The source has one field, Authorization Header: paste the value exactly as you set it in Procore, including any Bearer or Basic prefix. The source compares the whole value and accepts POST.
Securing Procore webhooks
Compare the whole Authorization header with the value you set on the hook, in constant time. Don't strip a prefix and don't base64-decode anything: Procore's own example, Basic c3a24b8208ac5199d083d54a1234e94b8864, is a token, not an encoded username and password.
const crypto = require('crypto');
const express = require('express');
// Exactly what you typed into Authorization Header, e.g. "Bearer 3f9c...".
const EXPECTED = process.env.PROCORE_AUTHORIZATION;
function safeEqual(received, expected) {
const a = Buffer.from(received, 'utf8');
const b = Buffer.from(expected, 'utf8');
// timingSafeEqual throws on unequal lengths, so check first.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
const app = express();
app.post('/webhooks/procore', express.raw({ type: '*/*' }), (req, res) => {
const received = req.get('authorization');
if (!EXPECTED || typeof received !== 'string' || !safeEqual(received, EXPECTED)) {
return res.sendStatus(401);
}
let event;
try {
event = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
// Queue the event, then respond. Fetch the full record from the REST API in a worker.
res.sendStatus(200);
});
The same check in Python:
import hmac
import os
EXPECTED = os.environ.get("PROCORE_AUTHORIZATION", "")
def verify_procore_request(headers) -> bool:
if not EXPECTED:
return False # fail closed when unconfigured
received = headers.get("authorization")
if not received:
return False
# Compare bytes: compare_digest raises TypeError on a non-ASCII str.
return hmac.compare_digest(received.encode("utf-8"), EXPECTED.encode("utf-8"))
A static Authorization value authenticates the sender, not the body. It can't detect a modified payload, and with no signed timestamp a captured request can be replayed and still pass. Use HTTPS, a long random token, and change the value on the hook and in your receiver if it might have leaked.
Make Procore webhooks production-ready. Hookdeck Event Gateway checks the Authorization Header value on every Procore event, stores each one durably, and lets you replay any delivery after a fix.
Procore webhook limitations and pain points
The Authorization Header is optional
The Problem: Requests arrive at your endpoint with nothing to authenticate them.
Why It Happens: Procore marks the field as optional, and a hook created without it sends no Authorization header.
Workarounds:
- Set the field on every hook, and fail closed when the header is missing.
- Check existing hooks, at company and project level, for an empty value.
How Hookdeck Can Help: Hookdeck's Procore source requires the value. Hookdeck answers Procore with a 200 either way, but a request with a missing or wrong header is recorded as failed verification and never reaches your destination.
The payload version is locked in
The Problem: Moving to a newer payload format means rebuilding the hook.
Why It Happens: Procore fixes the Payload Format when the hook is created. To change it, you delete the hook and create a new one, and pick the events again.
Workarounds:
- Choose the version deliberately, and record it with the receiver's code.
- During a switch, run old and new hooks side by side and deduplicate on the resource ID and event type until the old one is gone.
How Hookdeck Can Help: Both hooks can point at the same Hookdeck source, and Hookdeck filters and transformations can route or normalise the two formats while you migrate.
Retries are visible but not documented
The Problem: You can see that Procore retried or discarded an event, but not when or how often it will try.
Why It Happens: The hook's Events view shows statuses such as Retrying, Failed and Discarded, but the admin guides don't give a retry schedule or timeout.
Workarounds:
- Respond with
200quickly, and do the work afterwards. - Check the hook's Events view after an outage, and re-sync anything marked Failed or Discarded from the REST API.
How Hookdeck Can Help: Hookdeck accepts each delivery as soon as it arrives, so Procore records it as delivered, then delivers it to your service with retries you configure and lets you replay any event.
Duplicates and ordering
The Problem: An update is applied twice, or an older update overwrites a newer one.
Why It Happens: Retried deliveries can repeat an event, and nothing in the admin guides promises ordering.
Workarounds:
- Deduplicate on the resource name, resource ID, event type and timestamp.
- Re-read the record from the REST API before applying a change, so you always write the current state.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeated deliveries based on the fields you choose before they reach your service.
Best practices
Store the full header value
Keep the whole value, prefix included, as one string and compare it whole. A comparison against the bare token rejects every request.
Use one hook per purpose
Company and project hooks can both send to your endpoint. Keep track of which hook sends what, and use a separate endpoint path or value per hook if you need to tell them apart.
Fetch, don't trust, the details
Use the identifiers in the event to read the record from the REST API before acting on it. That also protects you if an event arrives late.
Acknowledge fast and process asynchronously
Verify the header, store the event, return 200, and call the REST API from a worker. See why to process webhooks asynchronously.
Conclusion
Procore hooks send a POST for each create, update or delete event on the resources you choose, authenticated only by the optional Authorization Header value. Set that value on every hook, compare the whole header in constant time, pick the payload version with care, and fetch records from the REST API when you need their full state.
Hookdeck Event Gateway sits in front of your receiver, checks the header, and keeps every event until your service has processed it.
Get started with Hookdeck for free and handle Procore webhooks reliably in minutes.