Skip to content

Webhooks

Webhooks deliver supported CHAMPREP events to your HTTPS endpoint. They reduce polling and let integrations respond to changes as they happen.

Webhook management requires the webhooks:manage scope.

Method Path Purpose
GET /v1/webhooks/events List the event types currently supported.
GET /v1/webhooks List configured webhook endpoints.
POST /v1/webhooks Register an HTTPS endpoint and event list.
PATCH /v1/webhooks/{webhookId} Update the URL, events, description, or active state.
DELETE /v1/webhooks/{webhookId} Delete an endpoint.
POST /v1/webhooks/{webhookId}/rotate-secret Rotate its signing secret.

Query /v1/webhooks/events instead of hard-coding an event catalog. Supported event types can expand as services add public events.

Terminal window
curl --fail-with-body \
--request POST \
--header "Authorization: Bearer $CHAMPREP_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"url": "https://example.com/webhooks/champrep",
"events": ["contact.created"],
"description": "Contact synchronization"
}' \
https://api.champrep.com/v1/webhooks

The signing secret is returned when the endpoint is created or its secret is rotated. Store it immediately in a secret manager; it is not a bearer API key and should be unique to the webhook endpoint.

Each delivery is an HTTPS POST with a JSON body:

{
"id": "delivery_identifier",
"type": "contact.created",
"timestamp": "2026-08-08T12:00:00Z",
"data": {}
}

Delivery headers include:

Header Purpose
X-CHAMPREP-Signature HMAC SHA-256 signature in sha256=<hex> form.
X-CHAMPREP-Timestamp Unix timestamp used in the signed content.
X-CHAMPREP-Delivery-Id Unique identifier for deduplication.
X-CHAMPREP-Event Event type for routing.

Compute HMAC SHA-256 over the exact UTF-8 bytes of <timestamp>.<raw-request-body> using the endpoint’s signing secret. Compare the expected and received signatures with a constant-time comparison.

import crypto from 'node:crypto';
export function verifyChamprepWebhook(rawBody, headers, secret) {
const timestamp = String(headers['x-champrep-timestamp'] || '');
const received = String(headers['x-champrep-signature'] || '');
const expected = `sha256=${crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex')}`;
const receivedBytes = Buffer.from(received);
const expectedBytes = Buffer.from(expected);
return receivedBytes.length === expectedBytes.length
&& crypto.timingSafeEqual(receivedBytes, expectedBytes);
}

Verify the raw body before a framework parses and re-serializes it. A re-serialized object can produce different bytes and fail verification.

  1. Reject a timestamp outside your chosen short tolerance window.
  2. Store processed delivery IDs for at least the duration of your retry window.
  3. Treat a repeated delivery ID as already processed and return a successful response.
  4. Perform business work idempotently whenever possible.

Return a successful 2xx response promptly and move expensive work to a queue. Timeouts, network failures, 429, and server errors can be retried. Persistent failures can cause an endpoint to be disabled, so monitor delivery state and correct failing URLs promptly.

Do not log signing secrets or complete sensitive payloads. Rotate the secret if it may have been exposed, then update the receiver before accepting new deliveries.