Webhook Signing
Verify outbound webhook deliveries from CleanEstimatePro with HMAC SHA-256.
Webhook Signing
You can register outbound webhook destinations with the external API or from Settings -> Integrations -> Webhooks.
Each destination gets a signing secret. CE Pro signs every delivery with HMAC SHA-256 so your server can verify authenticity before processing the payload.
Supported Events
lead.createdlead.status_changedestimate.createdestimate.acceptedjob.createdjob.completedinvoice.createdinvoice.paid
Delivery Headers
Every outbound delivery includes:
Content-Type: application/jsonX-Webhook-IDX-Webhook-SignatureX-Webhook-EventX-Webhook-TimestampX-Webhook-Attempt
Use X-Webhook-ID as the delivery identifier on your side for dedupe and replay tracking.
Example Payload
{
"event": "job.completed",
"timestamp": "2026-03-14T16:45:00.000Z",
"data": {
"job_id": "2d74d878-9a62-45da-84de-74d91c4a2b4d",
"status": "complete"
}
}Verify The Signature
Compute an HMAC SHA-256 signature of the raw request body using the webhook secret you stored when the destination was created.
Node.js
import crypto from 'crypto'
export function verifyCeProWebhook(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
const expectedBuffer = Buffer.from(expected, 'utf8')
const receivedBuffer = Buffer.from(signatureHeader || '', 'utf8')
return (
expectedBuffer.length === receivedBuffer.length &&
crypto.timingSafeEqual(expectedBuffer, receivedBuffer)
)
}Python
import hashlib
import hmac
def verify_cepro_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header or "")Retry Behavior
If your endpoint does not return a 2xx response, CE Pro queues retries using durable delivery records and retries after approximately 10s, 60s, and 300s.
Because retries happen, your endpoint should be idempotent. Store the X-Webhook-ID you have already processed and ignore duplicates.
Only live webhook destinations are processed in v1. Test API keys cannot register webhook endpoints, and the delivery worker ignores any non-live queue rows.
Inbound Provider Callbacks To CE Pro
CE Pro also receives inbound callbacks from providers such as Mailgun. Those provider-to-CE Pro requests do not use the outbound HMAC scheme above.
For inbound email routing, configure your provider to send:
Authorization: Bearer <EMAIL_INBOUND_WEBHOOK_SECRET>
Do not place the inbound secret in the query string. CE Pro no longer accepts query-string-only authentication for inbound email callbacks.
Example route target:
POST https://your-domain.com/api/webhooks/email-inbound
Authorization: Bearer <EMAIL_INBOUND_WEBHOOK_SECRET>URL Restrictions
CE Pro accepts only external HTTPS webhook URLs. Localhost, private-network, and internal-only targets are rejected for safety.
Related articles
Was this article helpful?
Still need help? Contact support