Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.closient.com/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks deliver real-time notifications to your server when events occur in Closient. Each webhook endpoint receives signed HTTP POST requests with JSON payloads.

Creating a Webhook Endpoint

Configure endpoints in Settings > Integrations in your Dashboard, or via the Integrations API. Each endpoint has:
  • URL — your HTTPS endpoint (HTTPS required in production)
  • Event types — which events to subscribe to (e.g., ["offer.updated", "product.recalled"]). Prefix matching is supported: subscribing to "offer" receives all offer.* events.
  • Filters — optional per-event-type filtering rules (AND logic between filter keys, OR within a key)
  • Signing secret — auto-generated 64-byte hex secret for payload verification

Payload Format

Every webhook delivery is a POST request with a JSON body:
{
  "id": "evt_a1b2c3d4e5f6",
  "type": "offer.updated",
  "created_at": "2026-04-01T12:00:00+00:00",
  "data": {
    "gtin": "00012345678905",
    "offer_id": "ack3p9tw6x7r"
  }
}
FieldTypeDescription
idstringUnique event identifier (evt_ prefix). Use for idempotency.
typestringDot-separated event type (e.g., offer.updated).
created_atstringISO 8601 timestamp of when the event was created.
dataobjectEvent-specific payload. Structure varies by event type.

Request Headers

Every delivery includes these headers:
HeaderExampleDescription
Content-Typeapplication/jsonAlways JSON.
X-Closient-Signaturet=1711972800,v1=5a3c...HMAC-SHA256 signature for verification.
X-Closient-Event-Typeoffer.updatedThe event type (same as type in body).
X-Closient-Delivery-IDd290f1ee-6c54-...Unique delivery attempt ID.
User-AgentClosient-Webhooks/1.0Identifies Closient as the sender.

Verifying Signatures

The X-Closient-Signature header uses a Stripe-style format that includes a timestamp for replay protection:
t=1711972800,v1=5a3c9f...
To verify:
  1. Extract the timestamp (t) and signature (v1) from the header.
  2. Construct the signed payload: {timestamp}.{raw_json_body}.
  3. Compute HMAC-SHA256 using your signing secret.
  4. Compare your computed signature with v1 using a constant-time comparison.
  5. Check the timestamp is within your tolerance window (recommended: 5 minutes).
import hashlib
import hmac
import time


def verify_closient_webhook(
    payload: bytes,
    signature_header: str,
    secret: str,
    tolerance_seconds: int = 300,
) -> bool:
    """Verify a Closient webhook signature.

    Args:
        payload: Raw request body bytes.
        signature_header: Value of X-Closient-Signature header.
        secret: Your endpoint's signing secret.
        tolerance_seconds: Max age in seconds (default 5 min).
    """
    # Parse header: t=123,v1=abc...
    parts = {}
    for element in signature_header.split(","):
        key, _, value = element.partition("=")
        parts[key.strip()] = value.strip()

    timestamp = parts.get("t", "")
    if not timestamp:
        return False

    # Replay protection
    age = abs(int(time.time()) - int(timestamp))
    if age > tolerance_seconds:
        return False

    # Compute expected signature
    signed_payload = f"{timestamp}.{payload.decode('utf-8')}"
    expected = hmac.new(
        secret.encode("utf-8"),
        signed_payload.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()

    # Check current signature (v1), then rotated signature (v1old)
    return (
        hmac.compare_digest(expected, parts.get("v1", ""))
        or hmac.compare_digest(expected, parts.get("v1old", ""))
    )
Always verify the signature before processing a webhook payload. Reject requests with missing, expired, or invalid signatures.

Secret Rotation

When you rotate a signing secret via the API (POST /endpoints/{id}/rotate-secret), the previous secret remains valid for a 24-hour grace period. During this window, deliveries include both signatures:
t=1711972800,v1=<current_secret_sig>,v1old=<previous_secret_sig>
Your verification code should check v1 first, then fall back to v1old. After the grace period, only v1 is included.

Delivery Lifecycle

Each delivery progresses through these statuses:
StatusDescription
pendingQueued for delivery.
deliveredYour endpoint returned a 2xx response.
failedNon-2xx response or connection error. Will be retried.
dead_letterAll retry attempts exhausted. Can be replayed manually.

Retry Policy

Failed deliveries are retried with exponential backoff and jitter:
AttemptApproximate Delay
1Immediate
230 seconds
32 minutes
415 minutes
51 hour
64 hours
712 hours
824 hours
After 8 attempts, the delivery moves to dead_letter. You can replay dead-lettered deliveries via the API:
POST /integrations/api/v1/webhooks/deliveries/{delivery_id}/replay/

Auto-Disable

If an endpoint has a 100% failure rate over the last 24 hours (with at least one delivery attempt), Closient automatically disables it and sends an email notification. Re-enable it from the Dashboard or API after resolving the issue.

Testing

Send a test event to verify your endpoint is reachable and signature verification works:
POST /integrations/api/v1/webhooks/endpoints/{endpoint_id}/test/
This delivers a test.ping event synchronously and returns the HTTP status code from your endpoint.

Best Practices

  • Return 2xx quickly — acknowledge receipt, then process asynchronously. Closient times out after 30 seconds.
  • Handle duplicates — use the id field for idempotency. The same event may be delivered more than once during retries.
  • Verify signatures — always validate X-Closient-Signature before processing. Reject unsigned or expired requests.
  • Use HTTPS — required in production. HTTP is only allowed in development.
  • Monitor health — check delivery status in the Dashboard or via the Integrations API. Address failures before they accumulate into auto-disable.
See the Integrations API reference for full endpoint management, delivery inspection, and filtering options.