Webhook Signing

Verify outbound webhook deliveries from CleanEstimatePro with HMAC SHA-256.

AdvancedownermanagerdeveloperUpdated 2026-03-16

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.created
  • lead.status_changed
  • estimate.created
  • estimate.accepted
  • job.created
  • job.completed
  • invoice.created
  • invoice.paid

Delivery Headers

Every outbound delivery includes:

  • Content-Type: application/json
  • X-Webhook-ID
  • X-Webhook-Signature
  • X-Webhook-Event
  • X-Webhook-Timestamp
  • X-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.

Was this article helpful?

Still need help? Contact support