> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.clossir.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.clossir.com/_mcp/server.

# Subscribe to Webhooks

GUIDES · SUBSCRIBE TO WEBHOOKS

This guide covers how to receive, verify, and process webhook events from Clossir. Webhooks
deliver real-time notifications when attestations are created, compliance checks complete,
and policies change — without polling.

## Prerequisites

* An organization with operations in progress ([Onboard an Organization](/guides/onboard-org))
* A publicly reachable HTTPS endpoint to receive webhook deliveries

## 1. Available event channels

Clossir delivers events across seven channels organized in two families:

### Attestation channels

| Channel                             | Fires when                                                    |
| ----------------------------------- | ------------------------------------------------------------- |
| `signum.attestation.created`        | A new attestation is issued                                   |
| `signum.attestation.revoked`        | An attestation is revoked                                     |
| `signum.attestation.sync_completed` | A cross-chain attestation sync completes (success or failure) |

### Compliance channels

| Channel                             | Fires when                                                      |
| ----------------------------------- | --------------------------------------------------------------- |
| `signum.compliance.check_requested` | A compliance check is queued                                    |
| `signum.compliance.check_completed` | A compliance check finishes                                     |
| `signum.compliance.rule_changed`    | A compliance policy configuration is modified                   |
| `signum.compliance.rule_synced`     | A compliance policy on-chain sync started, completed, or failed |

For the full payload schemas, see the [API Reference](/api/overview).

## 2. Configure your endpoint

Webhook subscriptions are configured through the [Clossir dashboard](https://app.dev.clossir.com/).
Navigate to your organization settings and add your webhook endpoint URL.

Your endpoint must:

* Accept `POST` requests over HTTPS
* Return a `2xx` status code within 10 seconds to acknowledge receipt
* Process events idempotently — the same event may be delivered more than once

There is no webhook subscription management API at this time. All webhook configuration
is done through the dashboard.

## 3. Payload structure

Every webhook delivery is a JSON payload containing the event data. Here is a representative
`signum.attestation.created` payload:

```json title="attestation-created-payload.json"
{
  "attestationId": "att_abc123",
  "userId": "usr_xyz789",
  "issuerId": "iss_def456",
  "attestationType": "kyc",
  "claimHash": "0xabc...def",
  "kycLevel": 2,
  "investorClass": 2,
  "countryCode": "US",
  "expiresAt": 1735689600000,
  "metadata": {},
  "timestamp": 1720000000000
}
```

Each event type has its own payload schema. See the [API Reference](/api/overview) for
the full catalog.

## 4. Tracing headers

Every webhook delivery includes [W3C Trace Context](https://www.w3.org/TR/trace-context/)
headers for distributed tracing:

| Header        | Required | Description                                                                 |
| ------------- | -------- | --------------------------------------------------------------------------- |
| `traceparent` | Yes      | W3C traceparent header (format: `{version}-{trace-id}-{parent-id}-{flags}`) |
| `tracestate`  | No       | W3C tracestate header with vendor-specific trace data                       |

Use these headers to correlate webhook deliveries with your internal request traces:

```ts title="extract-trace.ts"
function extractTraceContext(headers: Record<string, string>) {
  const traceparent = headers["traceparent"];
  // Format: 00-{32-char trace-id}-{16-char parent-id}-{2-char flags}
  const parts = traceparent.split("-");

  return {
    traceId: parts[1],
    parentId: parts[2],
    flags: parts[3],
  };
}
```

## 5. Verify the payload

Webhook signature verification is **coming soon**. Until the verification mechanism is
available, we recommend allowlisting the Clossir delivery IP range and validating payloads
against the expected schema.

When signature verification launches, each delivery will include a signature header that
you can verify using your webhook secret to confirm the payload originated from Clossir.

## 6. Build your webhook handler

Here is a complete webhook handler that receives, validates, and processes events:

```ts title="webhook-handler.ts"
import { serve } from "@hono/node-server";
import { Hono } from "hono";

const app = new Hono();

app.post("/webhooks/signum", async (c) => {
  const payload = await c.req.json();
  const traceparent = c.req.header("traceparent");

  // Log with trace context for observability
  console.log(`[${traceparent}] Received event`, payload);

  // Route by event type based on payload shape
  if ("attestationId" in payload && "claimHash" in payload) {
    await handleAttestationEvent(payload);
  } else if ("checkId" in payload) {
    await handleComplianceCheckEvent(payload);
  } else if ("changeType" in payload) {
    await handleRuleChangeEvent(payload);
  }

  // Return 200 to acknowledge receipt
  return c.json({ received: true });
});

async function handleAttestationEvent(event: any) {
  console.log(`Attestation ${event.attestationId} for user ${event.userId}`);
  // Process idempotently — use attestationId as dedup key
}

async function handleComplianceCheckEvent(event: any) {
  console.log(`Check ${event.checkId} passed: ${event.passed}`);
}

async function handleRuleChangeEvent(event: any) {
  console.log(`Rule ${event.changeType} changed for asset ${event.assetId}`);
}

serve({ fetch: app.fetch, port: 3000 });
```

## 7. Correlate with commands

When you issue a command that returns a resource ID in its `202` response (like
`attestationId`, `assetId`, or `checkId`), you can match that ID to the corresponding
webhook payload:

```ts title="correlate.ts"
// 1. Issue a command and capture the resource ID
const result = await signum.attestations.create({
  walletAddress: "0xabc...def",
  chainEid: 30101,
});
const attestationId = result.attestationId; // "att_xyz789"

// 2. Track the pending operation
await db.pendingOperations.insert({
  attestationId,
  type: "attestation_create",
  createdAt: new Date(),
});

// 3. In your webhook handler, match by resource ID
async function handleWebhook(payload: any) {
  const pending = await db.pendingOperations.findByAttestationId(
    payload.attestationId
  );
  if (pending) {
    console.log(`Attestation ${pending.attestationId} created on-chain`);
    await db.pendingOperations.markComplete(pending.attestationId);
  }
}
```

See [Async Operations](/getting-started/sdk/async-operations) for the full correlation pattern.

## Handle failures

* **Retries:** If your endpoint returns a non-2xx status or times out, Clossir retries
  delivery with exponential backoff.
* **Idempotency:** Always process events idempotently. Use the event's unique ID
  (`attestationId`, `checkId`, etc.) as a deduplication key.
* **Ordering:** Events may arrive out of order. Use the `timestamp` field to determine
  the sequence of events for a given resource.

## Reference

* [API Reference](/api/overview) — full catalog of event types and payload schemas
* [Async Operations](/getting-started/sdk/async-operations) — polling and webhook correlation

## Next steps

* [Getting Started](/getting-started/overview) — back to the overview
* [API Reference](/api/overview) — the full operation catalog