Skip to content

OEM Webhook Integration

Webhooks send job notifications to your server while the headless runtime is running. They are optional; local job polling continues when the remote destination is unavailable.

Configure delivery

Configure a separate random secret of at least 32 bytes through your installer/process secret handling. The runtime accepts HC_OEM_WEBHOOK_SECRET for initial setup and stores it in current-user protected storage, scoped to the partner/protocol profile. Do not pass it as a command-line argument or commit it to source control. Remove it from the launching environment after setup.

Start with your HTTPS receiver:

HeimerCleanOemRuntime.exe --partner-id sample-vendor --protocol-id OEM-CONTRACT-001 --webhook-url https://hooks.example.com/webhooks/heimerclean

The receiver must use public HTTPS on port 443 with a valid certificate. URL credentials, query-string secrets, redirects and private/loopback/link-local destinations are rejected. The destination is configured at process startup; local API callers cannot replace it in a job request.

Event envelope

{
  "schema_version": "1.0",
  "event_id": "0123456789abcdef0123456789abcdef",
  "sequence": 4,
  "type": "job.completed",
  "created_at": 1792490000.0,
  "partner_id": "sample-vendor",
  "protocol_id": "OEM-CONTRACT-001",
  "job_id": "fedcba9876543210fedcba9876543210",
  "data": {"state": "succeeded", "result": {"deleted_files": 12}}
}

Event types: job.queued, job.running, job.progress, job.completed, job.failed, job.cancelled. Events are notifications, not instructions to execute another cleanup. Failed/interrupted jobs can have partial effects; do not automatically run them again from a webhook.

Verify the exact raw bytes

Headers:

X-Heimer-Event-ID: <event_id>
X-Heimer-Timestamp: <Unix seconds>
X-Heimer-Signature: v1=<lowercase HMAC-SHA256 hex>

Calculate the signature over:

<timestamp>.<event_id>.<raw HTTP request body>

The separators are literal periods. Use the raw UTF-8 body, not parsed/reformatted JSON.

Receiver sequence:

  1. Bound the body size to 16 KB and require the expected schema.
  2. Reject timestamps more than five minutes from your current time. Keep clocks synchronized.
  3. Compare the HMAC using a constant-time comparison.
  4. Confirm the header and body event IDs match, and check the expected partner/protocol.
  5. Insert the verified event into a durable inbox with a unique event_id.
  6. Return 2xx after the inbox transaction commits. Process your application's business/UI work from that inbox.

Do not reserve an event ID before verifying its signature. A duplicate valid event should return 2xx without repeating business actions; a repeated ID with different bytes should be rejected and investigated.

The pilot SDK includes examples/webhook_receiver.py, which verifies signatures, checks the configured partner/protocol and stores a deduplicated SQLite inbox behind your HTTPS proxy. Configure HC_OEM_PARTNER_ID, HC_OEM_PROTOCOL_ID and the receiver secret in its environment. Add your application's inbox processing and production HTTP/TLS hosting before customer use.

Retry and recovery

The sender persists its outbox and retries unsuccessful deliveries with bounded backoff, up to eight attempts. Each retry keeps the same event ID/body and signs a fresh delivery timestamp. Non-2xx responses, including redirects, are not accepted as delivered.

GET /v1/status includes webhook_delivery counters. Exhausted deliveries are marked dead. A full pending outbox also marks new callbacks dead rather than blocking local maintenance; retrieve job results by polling and involve the integration operator. There is no public replay/redrive endpoint in this pilot.

Callbacks are not an exactly-once guarantee. Your receiver must handle duplicate and delayed notifications. History/outbox storage is bounded, so do not use webhook delivery as the only record of an important cleanup result.