Webhooks allow TrueGrade to push real-time event notifications to any external system that accepts HTTP POST requests. Use webhooks to trigger automations, update external dashboards, or sync data with your own systems.
| Category | Events |
|---|---|
| Compliance | document.submitted, document.verified, document.rejected, document.expired |
| Certifications | certification.advanced, certification.blocked, certification.certified |
| Cost | actual.created, actual.approved, budget.threshold_exceeded |
| Field | daily_report.submitted, issue.created, issue.resolved, issue.closed |
| Labor | timesheet.submitted, timesheet.approved, pay_application.issued, pay_application.approved |
| Procurement | rfq.issued, bid.submitted, bid.awarded |
| Users | user.invited, user.role_changed, user.deactivated |
Navigate to Administration → Integrations → Webhooks → Add Webhook:
Webhook endpoints must use HTTPS. HTTP endpoints are rejected.
TrueGrade sends a JSON POST request for each event:
{
"id": "wh_01hwz2k9m3x8y4b6c7d",
"event": "document.submitted",
"timestamp": "2026-04-22T14:32:00Z",
"organization_id": "org_01hwz2...",
"project_id": "proj_01hwz2...",
"payload": {
"subcontractor_id": "sub_01hwz2...",
"subcontractor_name": "Acme Insulation LLC",
"document_type": "certificate_of_insurance",
"document_id": "doc_01hwz2..."
}
}Every webhook request includes an X-TrueGrade-Signature header containing an HMAC-SHA256 signature. Verify it on your server to confirm the request came from TrueGrade:
import { createHmac, timingSafeEqual } from 'crypto'
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expected = createHmac('sha256', secret)
.update(payload)
.digest('hex')
return timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
)
}Always use timingSafeEqual (or equivalent constant-time comparison) to prevent timing attacks.
2xx response within 10 seconds2xx is received, the delivery is retried up to 5 times with exponential backoff (1m, 5m, 15m, 1h, 4h)If your endpoint is temporarily unavailable, acknowledge the webhooks quickly (respond 200 immediately, process asynchronously) rather than processing synchronously during the request. This avoids timeouts and unnecessary retries.
Every delivery attempt is logged with:
You can manually retry a failed delivery from the log view.