<!-- Source: https://www.wappi.dev/webhooks -->

# Webhooks

Webhooks let third parties such as Stripe call your API directly. A normal Wappi request is a POST with a `{ query, data }` body. A third party sends its own JSON, so Wappi gives webhooks their own path.

## Naming

Put every webhook in the **Webhook** namespace, so its function name starts with `Webhook_`. Versioning works the same as for any other endpoint.

```typescript
API/Webhook/StripeCheckout/1.0.0/index.ts   ->   Webhook_StripeCheckout_1_0_0
```

## Registering the endpoint

Add the webhook to `exports.Endpoints` in `API/index.ts`. Don't add it to `LoginRequired` or `PublicApiEndpoint`. The sender can't log in or send an API key, so the endpoint has to check the sender's signature itself.

```typescript
// API/index.ts
exports.Endpoints = {
  ...
  Webhook_StripeCheckout_1_0_0: true,
  // NewEndpointGoesHere
}
```

## Calling it

The sender POSTs to your API URL with the function name as the path. The body is whatever the sender sends, with no `query`/`data` wrapper.

```http
POST https://<your-api-url>/Webhook_StripeCheckout_1_0_0
```

## What the endpoint receives

Wappi passes `/Webhook_*` POSTs through unchanged. The endpoint's **Body** holds the parsed JSON plus the default body data, the request headers, and the raw request body as a string.

```typescript
// API/Webhook/StripeCheckout/1.0.0/types.ts
export interface Body extends WappiBaseNoAuthBody {
  headers?: Record<string, string | string[] | undefined>
  raw?: string
  [key: string]: any
}
```

Always verify signatures against `body.raw`. Re-serialising the parsed JSON changes the bytes, so the signature check would fail.

## Example: Stripe

```typescript
import Stripe from 'stripe'
import { DB } from '../../../CoreHelpers/types'
import { Body, Response } from './types'

export async function Webhook_StripeCheckout_1_0_0(
  body: Body,
  { prisma }: DB,
): Promise<Response> {
  try {
    const signature = body.headers?.['stripe-signature'] as string
    const event = stripe.webhooks.constructEvent(body.raw, signature, process.env.STRIPE_WEBHOOK_SECRET)

    if (event.type !== 'payment_intent.succeeded') {
      return { success: true, statusCode: 200, action: 'ignored' }
    }

    // ...handle the event

    return { success: true, statusCode: 200, action: 'processed' }
  } catch (err: any) {
    return { success: false, statusCode: 400 }
  }
}
```

## Status codes

The `statusCode` you return is the HTTP status the sender gets. Most senders, including Stripe, retry anything that isn't a 2xx. Return 200 when a retry can't help (the event is processed, a duplicate, ignored, or permanently invalid, such as a bad signature). Return 4xx or 5xx only when a retry could succeed. Senders can deliver the same event more than once, so make the handler safe to run twice.

## Troubleshooting

If you get this error, the request went through the normal query router instead of the webhook path. Check that the URL ends in `/Webhook_Name_1_0_0` with the exact casing, and that the deployed build includes the endpoint.

```json
{
  "statusCode": 500,
  "message": "Fail.. Query does not exist. Check the name and version",
  "error": "Query does not exist",
  "devMessages": [
    "Cannot Parse the body.data obj into JSON. Check the object structure in the data obj: { 'data': {} }",
    "Check the query name in the body JSON is correct. { 'query': 'Function_Name' }",
    "CORE ONLY:: There are no other functions"
  ]
}
```

URL: https://www.wappi.dev/webhooks
