Setting Up Webhooks
Webhooks allow your application to receive real-time HTTP notifications when events occur in Auris, such as a user signing up, a login failing, a role being assigned, or an organization being created. Instead of polling the API for changes, Auris pushes event payloads to your endpoint the moment something happens.
This guide walks you through creating a webhook receiver, registering it in the Auris Console, verifying signatures for security, and handling failures gracefully.
Why Webhooks
Without webhooks, your application would need to repeatedly poll Auris APIs to detect changes. This is inefficient, introduces latency (you only discover changes on your next poll cycle), and wastes resources on both sides. Webhooks invert this: Auris tells your application immediately when something happens.
Common use cases include:
- Sync user data to your database when a user is created or updated
- Trigger onboarding workflows when a new user signs up
- Revoke access in your system when a user is disabled or deleted
- Audit logging by streaming events to your SIEM
- Slack/Teams notifications when suspicious login activity is detected
Step 1: Create a Webhook Endpoint in Your Application
Your webhook endpoint is a standard HTTP POST handler that receives JSON payloads from Auris. Here is a complete Express.js example with signature verification:
import express from 'express'
import crypto from 'crypto'
const app = express()
// IMPORTANT: Use raw body for signature verification
app.post(
'/webhooks/auris',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-webhook-signature'] as string
const timestamp = req.headers['x-webhook-timestamp'] as string
const secret = process.env.AURIS_WEBHOOK_SECRET! // e.g. whsec_abc123...
// 1. Verify the signature
if (!verifyWebhookSignature(req.body, signature, timestamp, secret)) {
console.error('Webhook signature verification failed')
return res.status(401).json({ error: 'Invalid signature' })
}
// 2. Parse the event
const event = JSON.parse(req.body.toString())
console.log(`Received event: ${event.type}`, event.data)
// 3. Respond 200 immediately — process asynchronously
res.status(200).json({ received: true })
// 4. Handle the event asynchronously
handleWebhookEvent(event).catch((err) =>
console.error('Webhook handler error:', err)
)
}
)
function verifyWebhookSignature(
body: Buffer,
signature: string,
timestamp: string,
secret: string
): boolean {
// Reject if timestamp is older than 5 minutes (replay protection)
const eventTime = parseInt(timestamp, 10)
const now = Math.floor(Date.now() / 1000)
if (Math.abs(now - eventTime) > 300) {
return false
}
// Compute expected signature: HMAC-SHA256(timestamp.body)
const payload = `${timestamp}.${body.toString()}`
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
// Constant-time comparison to prevent timing attacks
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
)
} catch {
return false
}
}
async function handleWebhookEvent(event: {
type: string
data: Record<string, unknown>
}) {
switch (event.type) {
case 'user.created':
// Sync user to your local database
await syncUser(event.data)
break
case 'user.deleted':
// Remove user data from your system
await removeUser(event.data)
break
case 'login.suspicious':
// Alert your security team
await notifySecurityTeam(event.data)
break
default:
console.log(`Unhandled event type: ${event.type}`)
}
}
app.listen(3000, () => console.log('Webhook server running on port 3000'))Always use the raw request body (not the parsed JSON object) when computing the HMAC signature. Parsing and re-serializing JSON can change whitespace or key ordering, which invalidates the signature.
Step 2: Register the Webhook in Auris Console
- Open the Auris Console and navigate to Settings then Webhooks
- Click Create Webhook
- Enter your endpoint URL (e.g.,
https://api.yourapp.com/webhooks/auris) - Select the events you want to receive (or choose “All events”)
- Click Create
Auris generates a signing secret prefixed with whsec_ (e.g., whsec_k7Gm2x9pQ...). Copy this value immediately and store it securely in your environment variables. The full secret is only displayed once.
You can also register webhooks via the API:
curl -X POST https://auth.yourdomain.com/api/webhooks \
-H "Authorization: Bearer $AURIS_ACCESS_TOKEN" \
-H "x-tenant: your-tenant-id" \
-H "Content-Type: application/json" \
-d '{
"name": "Production webhook",
"url": "https://api.yourapp.com/webhooks/auris",
"events": ["user.created", "user.updated", "user.deleted", "login.failed"],
"isActive": true
}'Response:
{
"ok": true,
"data": {
"id": "whk_abc123",
"name": "Production webhook",
"url": "https://api.yourapp.com/webhooks/auris",
"events": ["user.created", "user.updated", "user.deleted", "login.failed"],
"signingSecret": "whsec_k7Gm2x9pQzR4vL8...",
"isActive": true,
"createdAt": "2026-01-15T10:00:00Z"
}
}Step 3: Verify Webhook Signatures
Every webhook delivery from Auris includes two headers for verification:
| Header | Description |
|---|---|
X-Webhook-Signature | HMAC-SHA256 hex digest of the payload |
X-Webhook-Timestamp | Unix timestamp (seconds) when the event was sent |
The signature is computed as:
HMAC-SHA256(whsec_secret, "${timestamp}.${rawBody}")Verification Algorithm
- Extract the
X-Webhook-SignatureandX-Webhook-Timestampheaders from the request - Check the timestamp is within 5 minutes of the current time (replay protection)
- Concatenate the timestamp, a literal period (
.), and the raw request body - Compute the HMAC-SHA256 of that string using your
whsec_signing secret - Compare your computed signature with the received signature using a constant-time comparison function
Here is a standalone verification function in TypeScript:
import crypto from 'crypto'
export function verifyAurisWebhook(
rawBody: string | Buffer,
signature: string,
timestamp: string,
secret: string
): boolean {
// Step 1: Replay protection — reject events older than 5 minutes
const ts = parseInt(timestamp, 10)
if (isNaN(ts) || Math.abs(Date.now() / 1000 - ts) > 300) {
return false
}
// Step 2: Compute expected signature
const body = typeof rawBody === 'string' ? rawBody : rawBody.toString('utf-8')
const payload = `${timestamp}.${body}`
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
// Step 3: Constant-time comparison
try {
return crypto.timingSafeEqual(
Buffer.from(signature, 'utf-8'),
Buffer.from(expected, 'utf-8')
)
} catch {
return false
}
}Using the Auris SDK for Verification
If you are using @auris/js, the SDK includes a built-in webhook verification utility:
import { verifyWebhookSignature } from '@auris/js/webhooks'
const isValid = await verifyWebhookSignature({
payload: rawBody,
signature: req.headers['x-webhook-signature'],
timestamp: req.headers['x-webhook-timestamp'],
secret: process.env.AURIS_WEBHOOK_SECRET!,
})Never skip signature verification in production. Without it, any attacker who discovers your endpoint URL can send forged events to your application.
Event Catalog
Auris sends events organized into the following categories. Each event payload includes a type field (e.g., user.created) and a data object containing the relevant resource.
User Events
| Event | Triggered when |
|---|---|
user.created | A new user is registered (signup, admin creation, SCIM provisioning, or SSO JIT) |
user.updated | User profile fields are modified |
user.deleted | A user is soft-deleted |
user.email_verified | A user verifies their email address |
user.password_changed | A user changes their password |
user.blocked | A user account is disabled |
user.unblocked | A user account is re-enabled |
Login Events
| Event | Triggered when |
|---|---|
login.succeeded | A user successfully authenticates |
login.failed | A login attempt fails (wrong credentials, locked account, etc.) |
login.mfa_required | MFA step-up is triggered during login |
login.suspicious | A login is flagged by suspicious login detection |
Role and Permission Events
| Event | Triggered when |
|---|---|
role.created | A new role is created |
role.updated | A role’s name, description, or permissions are modified |
role.deleted | A role is deleted |
role.assigned | A role is assigned to a user |
role.unassigned | A role is removed from a user |
Organization Events
| Event | Triggered when |
|---|---|
organization.created | A new organization is created |
organization.updated | Organization details are modified |
organization.deleted | An organization is deleted |
organization.member_added | A user joins an organization |
organization.member_removed | A user is removed from an organization |
organization.invitation_sent | An invitation is sent |
organization.invitation_accepted | An invitation is accepted |
Application Events
| Event | Triggered when |
|---|---|
application.created | A new application is registered |
application.updated | Application configuration is modified |
application.secret_rotated | An application’s client secret is rotated |
Example Payload
{
"id": "evt_abc123def456",
"type": "user.created",
"timestamp": "2026-01-15T10:30:00Z",
"data": {
"id": "usr_xyz789",
"email": "[email protected]",
"firstName": "Jane",
"lastName": "Doe",
"emailVerified": false,
"roles": [],
"createdAt": "2026-01-15T10:30:00Z"
}
}Handling Failures and Retries
If your endpoint returns a non-2xx status code or does not respond within 30 seconds, Auris considers the delivery failed and retries with exponential backoff:
| Attempt | Delay after failure |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 12 hours |
After 5 failed retries (6 total attempts), the delivery is marked as permanently failed. You can view failed deliveries and manually retry them in the Console under Settings then Webhooks then select a webhook then Deliveries.
If a webhook endpoint consistently fails, Auris automatically disables the webhook after 10 consecutive failed deliveries and sends a notification to tenant administrators. Re-enable it from the Console after fixing the underlying issue.
Testing Webhooks
Console Test Button
In the Auris Console, each webhook has a Test button that sends a synthetic event to your endpoint. This is the fastest way to verify your endpoint is reachable and signature verification is working correctly.
Local Development with ngrok
During development, your local server is not publicly reachable. Use a tunneling tool like ngrok to expose it:
# Start your local webhook server
node server.js # or: npx ts-node server.ts
# In another terminal, start ngrok
ngrok http 3000ngrok provides a public URL like https://a1b2c3d4.ngrok-free.app. Use this URL when registering the webhook in the Console:
https://a1b2c3d4.ngrok-free.app/webhooks/aurisAfter registering, use the Console test button or trigger a real event (e.g., create a user) to see the payload arrive in your local server.
Inspecting Deliveries
The Console webhook detail page shows a delivery log with:
- HTTP status code returned by your endpoint
- Response body (first 1 KB)
- Response time in milliseconds
- Number of retry attempts
- Timestamp of each delivery attempt
Best Practices
Respond 200 immediately. Your endpoint should return HTTP 200 as fast as possible, then process the event asynchronously (e.g., by queuing it). If your handler takes too long, Auris may time out and retry, leading to duplicate processing.
Implement idempotency. Every event includes a unique id field. Store processed event IDs and skip duplicates. Retries can deliver the same event multiple times.
async function handleWebhookEvent(event: { id: string; type: string; data: unknown }) {
// Check if already processed
const exists = await db.processedWebhookEvent.findUnique({
where: { eventId: event.id },
})
if (exists) {
console.log(`Skipping duplicate event: ${event.id}`)
return
}
// Process the event
await processEvent(event)
// Mark as processed
await db.processedWebhookEvent.create({
data: { eventId: event.id, processedAt: new Date() },
})
}Use HTTPS endpoints. Auris sends webhook payloads over HTTPS only. HTTP endpoints are rejected when registering the webhook.
Rotate secrets periodically. Use the Console or API to rotate your webhook signing secret. Auris supports secret rotation with a grace period where both old and new secrets are accepted.
curl -X POST https://auth.yourdomain.com/api/webhooks/whk_abc123/rotate-secret \
-H "Authorization: Bearer $AURIS_ACCESS_TOKEN" \
-H "x-tenant: your-tenant-id"Monitor delivery health. Set up alerts on your endpoint error rate. If you see a spike in failed deliveries, check your server logs and the Console delivery log for details.
Filter events at registration. Only subscribe to the events your application needs. Subscribing to all events generates unnecessary traffic and processing load.
Related Guides
- Log Streaming — Stream audit logs to external services like Datadog and Splunk
- Actions Engine — Execute custom logic during authentication flows
- Attack Protection — Security pipeline and suspicious login detection