kavachOS

Webhooks

Notify external services when auth events happen.

What webhooks do

Webhooks push signed HTTP POST requests to a URL you control whenever a KavachOS auth event occurs. Use them to sync user records, trigger onboarding flows, alert on suspicious logins, or feed events into your analytics pipeline.

Setup

import { createKavach, createWebhookModule } from 'kavachos';

const kavach = await createKavach({
  database: { provider: 'sqlite', url: 'kavach.db' },
  plugins: [
    createWebhookModule({
      secret: process.env.KAVACH_WEBHOOK_SECRET,
      endpoints: [
        {
          url: 'https://myapp.com/webhooks/kavach',
          events: ['user.created', 'auth.login', 'agent.created'],
        },
      ],
    }),
  ],
});

Store the webhook secret in an environment variable, not in source code. KavachOS uses it to sign every request with HMAC-SHA256.

Subscribing to events

Each endpoint subscribes to one or more event types. Use '*' to receive all events.

createWebhookModule({
  secret: process.env.KAVACH_WEBHOOK_SECRET,
  endpoints: [
    {
      url: 'https://myapp.com/webhooks/all',
      events: ['*'],
    },
    {
      url: 'https://myapp.com/webhooks/agents',
      events: ['agent.created', 'agent.revoked'],
    },
  ],
});

Event reference

EventFired when
user.createdA new human user registers
user.deletedA user account is deleted
auth.loginA successful login occurs
auth.logoutA session is terminated
auth.failedA login attempt fails
agent.createdA new agent identity is created
agent.revokedAn agent is revoked
delegation.grantedAn agent receives a delegation grant
delegation.revokedA delegation grant is removed
approval.requestedA human approval request is opened
approval.resolvedAn approval request is approved or denied

Request headers

Every webhook delivery includes these headers:

HeaderValue
X-Kavach-Signaturesha256=<hmac> — HMAC-SHA256 of the raw body
X-Kavach-EventEvent type, e.g. user.created
X-Kavach-DeliveryUnique UUID for this delivery attempt
X-Kavach-TimestampUnix timestamp (seconds) of delivery

Verifying signatures

Always verify the signature before trusting the payload.

import { createHmac, timingSafeEqual } from 'node:crypto';

function verifyWebhook(rawBody: Buffer, signature: string, secret: string): boolean {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
  return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

// Express handler
app.post('/webhooks/kavach', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-kavach-signature'] as string;
  if (!verifyWebhook(req.body, sig, process.env.KAVACH_WEBHOOK_SECRET!)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body.toString());
  // handle event...
  res.sendStatus(200);
});
async function verifyWebhook(rawBody: string, signature: string, secret: string): Promise<boolean> {
  const key = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(secret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign'],
  );
  const mac = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(rawBody));
  const expected = 'sha256=' + Array.from(new Uint8Array(mac)).map(b => b.toString(16).padStart(2, '0')).join('');
  return expected === signature;
}

Retry behavior

If your endpoint returns a non-2xx status or times out, KavachOS retries the delivery three times with exponential backoff:

AttemptDelay
130 seconds
25 minutes
330 minutes

After three failures the delivery is marked failed and no further retries occur.

Testing a webhook URL

Use the kavach.webhooks.test() method to send a synthetic ping event to any registered endpoint:

await kavach.webhooks.test('https://myapp.com/webhooks/kavach');

The test delivery sends { event: 'ping', timestamp: '...' } and respects the same signing and retry logic as real events.

Next steps

On this page