01 Software

Webhooks

Receive and process data change events

Webhooks

Receive events on your server when data changes.

Overview

When webhooks are configured, a POST request is sent to the specified URL when the following events occur:

  • create - New document created
  • update - Document updated

Webhook Setup

Create an API Route

app/api/webhook/route.ts
import { handleWebhook } from '@01.software/sdk/webhook'

export async function POST(request: Request) {
  return handleWebhook(request, async (event) => {
    console.log('Collection:', event.collection)
    console.log('Operation:', event.operation)
    console.log('Data:', event.data)
  })
}

Register the Webhook URL

Set the Webhook URL in the tenant settings of the 01.software Console.

https://your-domain.com/api/webhook

Payload Structure

WebhookEvent

interface WebhookEvent {
  collection: string
  operation: 'create' | 'update'
  data: any
  timestamp: string   // ISO 8601
}

JSON Example

{
  "collection": "products",
  "operation": "create",
  "data": {
    "id": "product-id",
    "title": "Product Name",
    "price": 10000
  },
  "timestamp": "2026-02-24T10:30:45.123Z"
}

SDK Handlers

handleWebhook

The basic webhook handler.

import { handleWebhook } from '@01.software/sdk/webhook'

export async function POST(request: Request) {
  return handleWebhook(request, async (event) => {
    if (event.collection === 'orders' && event.operation === 'create') {
      await sendOrderNotification(event.data)
    }
  })
}

createTypedWebhookHandler

Creates a type-safe handler for a specific collection.

import { handleWebhook, createTypedWebhookHandler } from '@01.software/sdk/webhook'

const handleOrderWebhook = createTypedWebhookHandler(
  'orders',
  async (event) => {
    // event.data type is inferred as Order
    console.log('Order:', event.data.orderNumber)
    console.log('Total:', event.data.totalAmount)

    if (event.operation === 'create') {
      await sendOrderConfirmation(event.data.email)
    }
  }
)

export async function POST(request: Request) {
  return handleWebhook(request, handleOrderWebhook)
}

isValidWebhookEvent

A type guard that validates webhook events.

import { isValidWebhookEvent } from '@01.software/sdk/webhook'

export async function POST(request: Request) {
  const body = await request.json()

  if (!isValidWebhookEvent(body)) {
    return new Response('Invalid event', { status: 400 })
  }

  // body is inferred as WebhookEvent type
  console.log(body.collection, body.operation)
}

Handling Multiple Collections

import { handleWebhook } from '@01.software/sdk/webhook'

export async function POST(request: Request) {
  return handleWebhook(request, async (event) => {
    switch (event.collection) {
      case 'orders':
        if (event.operation === 'create') {
          await sendOrderNotification(event.data)
        }
        break

      case 'products':
        if (event.operation === 'update') {
          await invalidateProductCache(event.data.id)
        }
        break

      case 'posts':
        revalidatePath('/blog')
        if (event.data.slug) {
          revalidatePath(`/blog/${event.data.slug}`)
        }
        break
    }
  })
}

Practical Examples

Order Notification

const handleOrderWebhook = createTypedWebhookHandler(
  'orders',
  async (event) => {
    if (event.operation === 'create') {
      await sendEmail({
        to: event.data.email,
        subject: `Order Confirmation - ${event.data.orderNumber}`,
        body: `Your order has been received.`
      })

      await sendSlackNotification({
        channel: '#orders',
        message: `New order: ${event.data.orderNumber}`
      })
    }
  }
)

Cache Invalidation

import { revalidatePath, revalidateTag } from 'next/cache'

const handlePostWebhook = createTypedWebhookHandler(
  'posts',
  async (event) => {
    revalidatePath('/blog')
    revalidatePath(`/blog/${event.data.slug}`)
    revalidateTag('posts')
  }
)

Inventory Sync

const handleProductWebhook = createTypedWebhookHandler(
  'products',
  async (event) => {
    if (event.operation === 'update') {
      await syncInventory({
        sku: event.data.sku,
        stock: event.data.stock
      })
    }
  }
)

Security

Signature Verification

The SDK's handleWebhook supports built-in HMAC-SHA256 signature verification via the secret option. The server signs the payload using PAYLOAD_SECRET and sends it in the x-webhook-signature header.

export async function POST(request: Request) {
  return handleWebhook(request, handler, {
    secret: process.env.WEBHOOK_SECRET!,
  })
}

IP Whitelist

export async function POST(request: Request) {
  const ip = request.headers.get('x-forwarded-for')

  if (!allowedIPs.includes(ip)) {
    return new Response('Forbidden', { status: 403 })
  }

  return handleWebhook(request, handler)
}

Testing

curl -X POST https://your-app.com/api/webhook \
  -H "Content-Type: application/json" \
  -d '{
    "collection": "orders",
    "operation": "create",
    "data": {
      "id": "order_123",
      "orderNumber": "260107123456",
      "email": "test@example.com"
    }
  }'

Error Handling

export async function POST(request: Request) {
  return handleWebhook(request, async (event) => {
    try {
      await processEvent(event)
    } catch (error) {
      console.error('Webhook processing error:', error)
      throw error // Throw error for retry
    }
  })
}

Webhook handlers should process quickly. Offload long-running tasks to background jobs.

Response Format

The webhook handler automatically returns the appropriate response.

  • Success: { "success": true, "message": "Webhook processed successfully" }
  • Failure: { "success": false, "error": "Invalid webhook event" }

Next Steps

On this page