Skip to main content

Webhook Setup

Webhooks allow you to receive real-time HTTP POST notifications when events occur in your workspace — including integration changes, transfer detection, and cost basis calculations. Instead of polling the API, webhooks push data to your server as events happen.

Which events you receive

Webhooks are configured on your developer workspace, but they fire for activity in the workspaces your users have granted you access to. The rule is:

You receive an event when it happens in a workspace covered by an active Kryptos Connect grant held by one of your OAuth clients.

So no extra wiring per user is needed — a user completing the Connect flow automatically becomes a source of events, and revoking their grant stops them.

One delivery per grant

Fan-out is per grant, not per workspace. If two of your OAuth clients each hold a live grant on the same end user, that user's events are delivered twice — once for each grant, each carrying its own grant_id and its own delivery id.

This is what makes a delivery attributable: grant_id tells you which of your customers the event belongs to, which a collapsed per-workspace delivery could not. See Identifying the user.

Deduplicate on X-Webhook-Id, not on event content

Retries of one delivery reuse the same X-Webhook-Id, so keying on it is the correct way to drop duplicates. It will not collapse the per-grant fan-out above, and it shouldn't — those are distinct deliveries for distinct grants.

This is the alternative to polling after a resync or a sync: subscribe to integration.updated and integration.failed and you can drop the poll loop entirely.

Prerequisites

Before you begin, ensure you have:

Step 1: Navigate to the Webhooks Section

Log in to the Kryptos Developer Portal and open your workspace. Scroll down to the Webhooks section.

Webhook Section

Click + Add Webhook to create a new webhook.

Step 2: Configure Your Webhook

Fill in the webhook configuration form:

Add Webhook

FieldDescription
Endpoint URLThe HTTPS URL where webhook events will be sent
Description (optional)A brief description to help identify this webhook
EventsSelect at least one event type to subscribe to

Available Events

Select at least one event to subscribe to. Kryptos supports events across three categories: Integration, Transfer Detection, and Cost Basis.

See the Webhook Events page for the full list of events, payload structures, and field descriptions.

Select the events you want to receive and click Add Webhook.

Step 3: Save Your Signing Secret

After creating the webhook, a signing secret is displayed. This secret is used to verify that incoming webhook payloads are genuinely from Kryptos.

Webhook Secret

Important

Save your signing secret securely — it will only be shown once! If you lose it, you can rotate the secret from the webhook settings.

Verifying Webhook Signatures

Every webhook delivery includes an HMAC-SHA256 signature in the X-Webhook-Signature header. Use your signing secret to verify the payload authenticity.

Delivery Headers

Each webhook request includes the following headers:

HeaderDescription
X-Webhook-SignatureHMAC-SHA256 hex digest of the request body
X-Webhook-EventEvent type (e.g., integration.created)
X-Webhook-IdUnique delivery ID
X-Webhook-TimestampISO 8601 timestamp of the delivery
Content-Typeapplication/json

There is no header for grant_id or workspace_id — they are envelope fields in the JSON body. Parse the body to read them.

Signature Verification Examples

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}

// Express.js example
app.post('/webhooks/kryptos', express.raw({ type: 'application/json' }), async (req, res) => {
const signature = req.headers['x-webhook-signature'];
const event = req.headers['x-webhook-event'];

if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}

const payload = JSON.parse(req.body);

// Which of your customers this belongs to. Store grant_id at connect time and look it up here.
const { grant_id: grantId, workspace_id: workspaceId } = payload;
const customer = await findCustomerByGrantId(grantId);

switch (event) {
case 'integration.created':
console.log('New integration:', payload.data);
break;
case 'integration.updated':
console.log('Integration updated:', payload.data);
break;
case 'integration.deleted':
console.log('Integration removed:', payload.data);
break;
case 'integration.failed':
console.log('Integration failed:', payload.data);
break;
case 'transfer_detection.started':
case 'transfer_detection.completed':
case 'transfer_detection.failed':
console.log(`Transfer detection ${payload.data.status}:`, payload.data);
break;
case 'costbasis.started':
case 'costbasis.completed':
case 'costbasis.failed':
console.log(`Cost basis ${payload.data.status}:`, payload.data);
break;
}

res.status(200).send('OK');
});

Retry Behavior

Kryptos will retry failed webhook deliveries with exponential backoff:

  • Maximum retries: 2 (3 total attempts)
  • Timeout: 10 seconds per delivery
  • Retryable status codes: 429, 500, 502, 503, 504

Your endpoint should return a 2xx status code within 10 seconds to acknowledge receipt. Any other response (or a timeout) triggers a retry.

Managing Webhooks

From the Developer Portal, you can:

  • Edit a webhook's URL, description, or subscribed events
  • Rotate the secret if your signing secret is compromised
  • Disable a webhook temporarily without deleting it
  • Delete a webhook when it is no longer needed

Best Practices

  1. Always verify signatures — Check the X-Webhook-Signature header on every request to ensure payloads are from Kryptos.
  2. Respond quickly — Return a 200 response immediately and process the event asynchronously. Long-running handlers risk timeouts and unnecessary retries.
  3. Handle duplicates — Use the X-Webhook-Id header to deduplicate events in case of retries.
  4. Route on grant_id — Store it when a user completes the Connect flow, and use it to attribute every delivery. Don't rely on walletId; it is absent from half the event families and arrives after the fact in the other half.
  5. Use HTTPS — Your endpoint must use HTTPS to protect webhook data in transit.
  6. Monitor failures — Track failed deliveries and investigate persistent errors.

Next Steps