Sitemark webhooks push real-time event notifications to your server via HTTP POST. Configure a webhook endpoint in your account settings and Sitemark will deliver a signed JSON payload each time a relevant event occurs — no polling required.
Each webhook delivery is an HTTP POST to your configured endpoint with a JSON body. The outer envelope is consistent across all events; the data object contains event-specific fields.
job.completedFires when a job is marked complete{
"event": "job.completed",
"created_at": "2026-05-28T18:32:00Z",
"data": {
"job_id": "job_xyz789",
"name": "Main St Sewer Lateral — Sta 0+00 to 4+50",
"completed_at": "2026-05-28T18:31:52Z",
"shot_count": 42,
"pass_rate": 0.97,
"report_url": "https://sitemark.ai/reports/job_xyz789.pdf"
}
}shot.loggedFires each time a grade shot is recorded{
"event": "shot.logged",
"created_at": "2026-05-28T17:45:11Z",
"data": {
"shot_id": "shot_abc123",
"job_id": "job_xyz789",
"elevation": 312.47,
"design_grade": 312.50,
"delta": -0.03,
"pass": true,
"station": "1+25",
"offset": "0.0",
"logged_at": "2026-05-28T17:45:10Z"
}
}report.generatedFires when an as-built report is ready{
"event": "report.generated",
"created_at": "2026-05-28T18:33:05Z",
"data": {
"report_id": "rpt_def456",
"job_id": "job_xyz789",
"format": "pdf",
"url": "https://sitemark.ai/reports/rpt_def456.pdf",
"expires_at": "2026-05-29T18:33:05Z"
}
}Every webhook delivery includes a X-Sitemark-Signature header. The value is an HMAC-SHA256 digest of the raw request body, signed with your webhook secret. Always verify this signature before processing a webhook payload to ensure it originated from Sitemark and has not been tampered with.
Verification steps:
X-Sitemark-Signature header.400 if the signatures do not match.Verify in Node.js
const crypto = require('crypto')
function verifySitemarkWebhook(rawBody, signature, secret) {
const digest = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(digest),
Buffer.from(signature)
)
}
// Express example — use express.raw() for this route
app.post('/webhooks/sitemark', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-sitemark-signature']
const secret = process.env.SITEMARK_WEBHOOK_SECRET
if (!verifySitemarkWebhook(req.body, sig, secret)) {
return res.status(400).send('Invalid signature')
}
const event = JSON.parse(req.body)
console.log('Received event:', event.event)
res.status(200).send('OK')
})Verify in Python
import hmac
import hashlib
import json
import os
from flask import Flask, request, abort
app = Flask(__name__)
def verify_sitemark_webhook(raw_body: bytes, signature: str, secret: str) -> bool:
digest = hmac.new(
secret.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(digest, signature)
@app.route('/webhooks/sitemark', methods=['POST'])
def handle_webhook():
sig = request.headers.get('X-Sitemark-Signature', '')
secret = os.environ['SITEMARK_WEBHOOK_SECRET']
if not verify_sitemark_webhook(request.data, sig, secret):
abort(400, 'Invalid signature')
event = json.loads(request.data)
print(f"Received event: {event['event']}")
return 'OK', 200