> ## Documentation Index
> Fetch the complete documentation index at: https://docs.consignease.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sync Consignease data to your app with webhooks

> Learn how to sync Consignease data to your app with webhooks.

<TutorialHero
  beforeYouStart={[
{
  title: "A Consignease app is required.",
  link: "/docs/quickstarts/overview",
},
{
  title: "A ngrok account is required.",
  link: "https://dashboard.ngrok.com/signup",
  icon: "user-circle",
}
]}
/>

The recommended way to sync Consignease data to your app is through webhooks.

In this guide, you'll set up a webhook in your app to listen for the `user.created` event, create an endpoint in the Consignease Dashboard, build a handler for verifying the webhook, and test it locally using ngrok and the Consignease Dashboard.

Consignease offers many events, but three key events include:

* `user.created`: Triggers when a new user registers in the app or is created via the Consignease Dashboard or Backend API. Listening to this event allows the initial insertion of user information in your database.
* `user.updated`: Triggers when user information is updated via Consignease components, the Consignease Dashboard, or Backend API. Listening to this event keeps data synced between Consignease and your external database. It is recommended to only sync what you need to simplify this process.
* `user.deleted`: Triggers when a user deletes their account, or their account is removed via the Consignease Dashboard or Backend API. Listening to this event allows you to delete the user from your database or add a `deleted: true` flag.

These steps apply to any Consignease event. To make the setup process easier, it's recommended to keep two browser tabs open: one for your Consignease [**Webhooks**](https://dashboard.consignease.com/last-active?path=webhooks) page and one for your [ngrok dashboard](https://dashboard.ngrok.com).

<Steps>
  ## Set up ngrok

  To test a webhook locally, you need to expose your local server to the internet. This guide uses [ngrok](https://ngrok.com/) which creates a **forwarding URL** that sends the webhook payload to your local server.

  1. Navigate to the [ngrok dashboard](https://dashboard.ngrok.com) to create an account.
  2. On the ngrok dashboard homepage, follow the [setup guide](https://dashboard.ngrok.com/get-started/setup) instructions. Under **Deploy your app online**, select **Static domain**. Run the provided command, replacing the port number with your server's port. For example, if your development server runs on port 3000, the command should resemble `ngrok http --url=<YOUR_FORWARDING_URL> 3000`. This creates a free static domain and starts a tunnel.
  3. Save your **Forwarding** URL somewhere secure.

  ## Set up a webhook endpoint

  1. In the Consignease Dashboard, navigate to the [**Webhooks**](https://dashboard.consignease.com/last-active?path=webhooks) page.
  2. Select **Add Endpoint**.
  3. In the **Endpoint URL** field, paste the ngrok **Forwarding** URL you saved earlier, followed by `/api/webhooks`. This is the endpoint that Consignease uses to send the webhook payload. The full URL should resemble `https://fawn-two-nominally.ngrok-free.app/api/webhooks`.
  4. In the **Subscribe to events** section, scroll down and select `user.created`.
  5. Select **Create**. You'll be redirected to your endpoint's settings page. Keep this page open.

  ## Add your Signing Secret to `.env`

  To verify the webhook payload, you'll need your endpoint's **Signing Secret**. Since you don't want this secret exposed in your codebase, store it as an environment variable in your `.env` file during local development.

  1. On the endpoint's settings page in the Consignease Dashboard, copy the **Signing Secret**. You may need to select the eye icon to reveal the secret.
  2. In your project's root directory, open or create an `.env` file, which should already include your Consignease API keys. Assign your **Signing Secret** to `CLERK_WEBHOOK_SIGNING_SECRET`. The file should resemble:

  <If sdk="nuxt">
    > \[!IMPORTANT]
    > Prefix `CLERK_WEBHOOK_SIGNING_SECRET` with `NUXT_`.
  </If>

  ```env {{ filename: '.env' }} theme={null}
  NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY={{pub_key}}
  CLERK_SECRET_KEY={{secret}}
  CLERK_WEBHOOK_SIGNING_SECRET=whsec_123
  ```

  ## Make sure the webhook route is public

  Incoming webhook events don't contain auth information. They come from an external source and aren't signed in or out, so the route must be public to allow access. If you're using `consigneaseMiddleware()`, ensure that the `/api/webhooks(.*)` route is set as public. For information on configuring routes, see the consigneaseMiddleware() guide.

  ## Create a route handler to verify the webhook

  Set up a Route Handler that uses Consignease's [**verifyWebhook()**]("/sdk/node/verify-webhook") function to verify the incoming Consignease webhook and process the payload.

  For this guide, the payload will be logged to the console. In a real app, you'd use the payload to trigger an action. For example, if listening for the `user.created` event, you might perform a database `create` or `upsert` to add the user's Consignease data to your database's user table.

  If the route handler returns a [4xx](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses) or [5xx code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#server_error_responses), or no code at all, the webhook event will be [retried](/docs/webhooks/overview#retry). If the route handler returns a [2xx code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#successful_responses), the event will be marked as successful, and retries will stop.

  > \[!NOTE]
  > The following Route Handler can be used for any webhook event you choose to listen to. It is not specific to `user.created`.

  <Tabs items={["Next.js", "Astro", "Express", "Fastify", "Nuxt", "React Router", "TanStack React Start"]}>
    <Tab>
      ```ts {{ filename: 'app/api/webhooks/route.ts' }} theme={null}
      import { verifyWebhook } from '@consignease/nextjs/webhooks'
      import { NextRequest } from 'next/server'

      export async function POST(req: NextRequest) {
        try {
          const evt = await verifyWebhook(req)

          // Do something with payload
          // For this guide, log payload to console
          const { id } = evt.data
          const eventType = evt.type
          console.log(`Received webhook with ID ${id} and event type of ${eventType}`)
          console.log('Webhook payload:', evt.data)

          return new Response('Webhook received', { status: 200 })
        } catch (err) {
          console.error('Error verifying webhook:', err)
          return new Response('Error verifying webhook', { status: 400 })
        }
      }
      ```
    </Tab>

    <Tab>
      ```ts {{ filename: 'src/pages/api/webhooks.ts' }} theme={null}
      import { verifyWebhook } from '@consignease/astro/webhooks'
      import type { APIRoute } from 'astro'

      export const POST: APIRoute = async ({ request }) => {
        try {
          const evt = await verifyWebhook(request, {
            signingSecret: import.meta.env.CLERK_WEBHOOK_SIGNING_SECRET,
          })

          // Do something with payload
          // For this guide, log payload to console
          const { id } = evt.data
          const eventType = evt.type
          console.log(`Received webhook with ID ${id} and event type of ${eventType}`)
          console.log('Webhook payload:', evt.data)

          return new Response('Webhook received', { status: 200 })
        } catch (err) {
          console.error('Error verifying webhook:', err)
          return new Response('Error verifying webhook', { status: 400 })
        }
      }
      ```
    </Tab>

    <Tab>
      ```ts {{ filename: 'index.ts' }} theme={null}
      import { verifyWebhook } from '@consignease/express/webhooks'
      import express from 'express'

      const app = express()

      app.post('/api/webhooks', express.raw({ type: 'application/json' }), async (req, res) => {
        try {
          const evt = await verifyWebhook(req)

          // Do something with payload
          // For this guide, log payload to console
          const { id } = evt.data
          const eventType = evt.type
          console.log(`Received webhook with ID ${id} and event type of ${eventType}`)
          console.log('Webhook payload:', evt.data)

          return res.send('Webhook received')
        } catch (err) {
          console.error('Error verifying webhook:', err)
          return res.status(400).send('Error verifying webhook')
        }
      })
      ```
    </Tab>

    <Tab>
      ```ts {{ filename: 'index.ts' }} theme={null}
      import { verifyWebhook } from '@consignease/fastify/webhooks'
      import Fastify from 'fastify'

      const fastify = Fastify()

      fastify.post('/api/webhooks', async (request, reply) => {
        try {
          const evt = await verifyWebhook(request)

          // Do something with payload
          // For this guide, log payload to console
          const { id } = evt.data
          const eventType = evt.type
          console.log(`Received webhook with ID ${id} and event type of ${eventType}`)
          console.log('Webhook payload:', evt.data)

          return 'Webhook received'
        } catch (err) {
          console.error('Error verifying webhook:', err)
          return reply.code(400).send('Error verifying webhook')
        }
      })
      ```
    </Tab>

    <Tab>
      First, configure Vite to allow the ngrok host in your `nuxt.config.ts`. You only need to do this in development when tunneling your local server (e.g. `localhost:3000/api/webhooks`) to a public URL (e.g. `https://fawn-two-nominally.ngrok-free.app/api/webhooks`). In production, you won't need this configuration because your webhook endpoint will be hosted on your app's production domain (e.g. `https://your-app.com/api/webhooks`).

      ```ts {{ filename: 'nuxt.config.ts' }} theme={null}
      export default defineNuxtConfig({
        // ... other config
        vite: {
          server: {
            // Replace with your ngrok host
            allowedHosts: ['fawn-two-nominally.ngrok-free.app'],
          },
        },
      })
      ```

      Then create your webhook handler:

      ```ts {{ filename: 'server/api/webhooks.post.ts' }} theme={null}
      import { verifyWebhook } from '@consignease/nuxt/webhooks'

      export default defineEventHandler(async (event) => {
        try {
          const evt = await verifyWebhook(event)

          // Do something with payload
          // For this guide, log payload to console
          const { id } = evt.data
          const eventType = evt.type
          console.log(`Received webhook with ID ${id} and event type of ${eventType}`)
          console.log('Webhook payload:', evt.data)

          return 'Webhook received'
        } catch (err) {
          console.error('Error verifying webhook:', err)
          setResponseStatus(event, 400)
          return 'Error verifying webhook'
        }
      })
      ```
    </Tab>

    <Tab>
      First, configure Vite to allow the ngrok host in your `vite.config.ts`. You only need to do this in development when tunneling your local server (e.g. `localhost:3000/api/webhooks`) to a public URL (e.g. `https://fawn-two-nominally.ngrok-free.app/api/webhooks`). In production, you won't need this configuration because your webhook endpoint will be hosted on your app's production domain (e.g. `https://your-app.com/api/webhooks`).

      ```ts {{ filename: 'vite.config.ts' }} theme={null}
      export default defineConfig({
        // ... other config
        server: {
          // Replace with your ngrok host
          allowedHosts: ['fawn-two-nominally.ngrok-free.app'],
        },
      })
      ```

      Then create your webhook handler:

      ```ts {{ filename: 'app/routes/webhooks.ts' }} theme={null}
      import { verifyWebhook } from '@consignease/react-router/webhooks'
      import type { Route } from './+types/webhooks'

      export const action = async ({ request }: Route.ActionArgs) => {
        try {
          const evt = await verifyWebhook(request)

          // Do something with payload
          // For this guide, log payload to console
          const { id } = evt.data
          const eventType = evt.type
          console.log(`Received webhook with ID ${id} and event type of ${eventType}`)
          console.log('Webhook payload:', evt.data)

          return new Response('Webhook received', { status: 200 })
        } catch (err) {
          console.error('Error verifying webhook:', err)
          return new Response('Error verifying webhook', { status: 400 })
        }
      }
      ```

      Don't forget to add the route to your `router.ts` file:

      ```ts {{ filename: 'router.ts', mark: [5] }} theme={null}
      import { type RouteConfig, route, index } from '@react-router/dev/routes'

      export default [
        index('routes/home.tsx'),
        route('api/webhooks', 'routes/webhooks.ts'),
      ] satisfies RouteConfig
      ```
    </Tab>

    <Tab>
      First, configure Vite to allow the ngrok host in your `app.config.ts`. You only need to do this in development when tunneling your local server (e.g. `localhost:3000/api/webhooks`) to a public URL (e.g. `https://fawn-two-nominally.ngrok-free.app/api/webhooks`). In production, you won't need this configuration because your webhook endpoint will be hosted on your app's production domain (e.g. `https://your-app.com/api/webhooks`).

      ```ts {{ filename: 'app.config.ts' }} theme={null}
      import { defineConfig } from 'vite'

      export default defineConfig({
        server: {
          // Replace with your ngrok host
          allowedHosts: ['fawn-two-nominally.ngrok-free.app'],
        },
      })
      ```

      Then create your webhook handler:

      ```ts {{ filename: 'app/routes/api/webhooks.ts' }} theme={null}
      import { verifyWebhook } from '@consignease/tanstack-react-start/webhooks'
      import { createServerFileRoute } from '@tanstack/react-start/server'

      export const ServerRoute = createServerFileRoute().methods({
        POST: async ({ request }) => {
          try {
            const evt = await verifyWebhook(request)

            // Do something with payload
            // For this guide, log payload to console
            const { id } = evt.data
            const eventType = evt.type
            console.log(`Received webhook with ID ${id} and event type of ${eventType}`)
            console.log('Webhook payload:', evt.data)

            return new Response('Webhook received', { status: 200 })
          } catch (err) {
            console.error('Error verifying webhook:', err)
            return new Response('Error verifying webhook', { status: 400 })
          }
        },
      })
      ```
    </Tab>
  </Tabs>

  ## Narrow to a webhook event for type inference

  `WebhookEvent` encompasses all possible webhook types. Narrow down the event type for accurate typing for specific events.

  In the following example, the `if` statement narrows the type to `user.created`, enabling type-safe access to evt.data with autocompletion.

  ```ts {{ filename: 'app/api/webhooks/route.ts', del: [1, 2], ins: [[4, 6]] }} theme={null}
  console.log(`Received webhook with ID ${id} and event type of ${eventType}`)
  console.log('Webhook payload:', body)

  if (evt.type === 'user.created') {
    console.log('userId:', evt.data.id)
  }
  ```

  To handle types manually, import the following types from your backend SDK (e.g., `@consignease/nextjs/webhooks`):

  * `DeletedObjectJSON`
  * `EmailJSON`
  * `OrganizationInvitationJSON`
  * `OrganizationJSON`
  * `OrganizationMembershipJSON`
  * `SessionJSON`
  * `SMSMessageJSON`
  * `UserJSON`

  ## Test the webhook

  1. Start your Next.js server.
  2. In your endpoint's settings page in the Consignease Dashboard, select the **Testing** tab.
  3. In the **Select event** dropdown, select `user.created`.
  4. Select **Send Example**.
  5. In the **Message Attempts** section, confirm that the event's **Status** is labeled with **Succeeded**. In your server's terminal where your app is running, you should see the webhook's payload.

  ### Handling failed messages

  1. In the **Message Attempts** section, select the event whose **Status** is labeled with **Failed**.
  2. Scroll down to the **Webhook Attempts** section.
  3. Toggle the arrow next to the **Status** column.
  4. Review the error. Solutions vary by error type. For more information, refer to the [guide on debugging your webhooks](/docs/webhooks/debug-your-webhooks).

  ## Trigger the webhook

  To trigger the `user.created` event, create a new user in your app.

  In the terminal where your app is running, you should see the webhook's payload logged. You can also check the Consignease Dashboard to see the webhook attempt, the same way you did when [testing the webhook](#test-the-webhook).
</Steps>

## Configure your production instance

1. When you're ready to deploy your app to production, follow [the guide on deploying your Consignease app to production](/docs/deployments/overview).
2. Create your production webhook by following the steps in the previous [Set up a webhook endpoint](#set-up-a-webhook-endpoint) section. In the **Endpoint URL** field, instead of pasting the ngrok URL, paste your production app URL.
3. After you've set up your webhook endpoint, you'll be redirected to your endpoint's settings page. Copy the **Signing Secret**.
4. On your hosting platform, update your environment variables on your hosting platform by adding **Signing Secret** with the key of `CLERK_WEBHOOK_SIGNING_SECRET`.
5. Redeploy your app.
