---
title: "Webhooks"
description: "Configure webhooks to receive real-time notifications for onchain events and loyalty actions."
keywords: ["Web3 webhooks", "blockchain webhooks", "onchain event notifications", "smart contract webhooks"]
icon: "webhook"
iconType: "regular"
---

> **For AI agents:** the complete documentation index is at [llms.txt](https://docs.snagsolutions.io/llms.txt). Append `.md` to any page URL for its markdown version.

## Overview

Stratus subscriptions deliver events via webhooks, enabling your systems to respond immediately to key events. This guide explains how to set up and validate webhook notifications.

Webhooks are triggered by a scheduled job that runs **every minute**. During each run, Stratus processes all recent changes recorded since the previous run. Once updates are batched and applied, webhook events are dispatched to all destinations subscribed to the affected resources. This ensures near real-time delivery without missing intermediate updates.

## Getting Started

<Steps>
  <Step title="Enable Webhooks">
    Toggle webhooks within your subscription settings
  </Step>
  <Step title="Configure Endpoint">
    Provide the URL where your system will accept POST requests
  </Step>
  <Step title="Store Signing Key">
    Save the `Signing Key` from your subscription settings for payload validation
  </Step>
</Steps>

## Implementation Guide

### Webhook Validation

<Note>
  Each webhook payload includes a signature header (`x-signature`) that should be validated to ensure authenticity.
</Note>

<CodeGroup>

```javascript Next.js Example
// https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
export const config = {
  api: {
    bodyParser: false,
  },
}

export default async function handler(req, res) {
  try {
    if (req.method !== 'POST') {
      res.setHeader('Allow', 'POST')
      return res.status(405).json({ error: 'Method Not Allowed' })
    }

    const rawBody = (await getRawBody(req)).toString('utf-8')

    const signature = req.headers['x-signature']
    if (!signature) {
      console.error('Missing signature')
      return res.status(400).json({ error: 'Missing signature' })
    }

    const secret = process.env.WEBHOOK_SECRET
    if (!secret) {
      console.error('Server misconfiguration: Missing WEBHOOK_SECRET')
      return res
        .status(500)
        .json({ error: 'Server misconfiguration: Missing WEBHOOK_SECRET' })
    }

    const computedSignature = crypto
      .createHmac('sha256', secret)
      .update(rawBody)
      .digest('hex')

    if (
      !crypto.timingSafeEqual(
        new Uint8Array(Buffer.from(computedSignature)),
        new Uint8Array(Buffer.from(signature))
      )
    ) {
      console.error('Invalid signature')
      return res.status(401).json({ error: 'Invalid signature' })
    }

    const payload = JSON.parse(rawBody)
    payload.forEach((event) => {
      console.log('Received event:', event)
    })

    res.status(200).json({ status: 'Webhook processed successfully' })
  } catch (error) {
    console.error('Webhook processing error:', error)
    res.status(500).json({ error: 'Internal Server Error' })
  }
}
```

</CodeGroup>

## Important Considerations

<Columns cols={3}>
  <Card title="Batching" icon="layer-group">
    Multiple events may be batched into a single webhook payload
  </Card>
  <Card title="Retry Logic" icon="rotate">
    Failed webhooks are retried exponentially up to five times
  </Card>
  <Card title="Security" icon="shield-check">
    Always validate webhook signatures using your WEBHOOK_SECRET
  </Card>
</Columns>

<Warning>
  Never expose your WEBHOOK_SECRET in client-side code or public repositories. Always store it securely in environment variables.
</Warning>
