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.
Put every webhook in the Webhook namespace, so its function name starts with Webhook_. Versioning works the same as for any other endpoint.
1
2API/Webhook/StripeCheckout/1.0.0/index.ts -> Webhook_StripeCheckout_1_0_0
3Add 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.
1
2// API/index.ts
3exports.Endpoints = {
4 ...
5 Webhook_StripeCheckout_1_0_0: true,
6 // NewEndpointGoesHere
7}
8The 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.
1
2POST https://<your-api-url>/Webhook_StripeCheckout_1_0_0
3Wappi 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.
1
2// API/Webhook/StripeCheckout/1.0.0/types.ts
3export interface Body extends WappiBaseNoAuthBody {
4 headers?: Record<string, string | string[] | undefined>
5 raw?: string
6 [key: string]: any
7}
8Always verify signatures against body.raw. Re-serialising the parsed JSON changes the bytes, so the signature check would fail.
1
2import Stripe from 'stripe'
3import { DB } from '../../../CoreHelpers/types'
4import { Body, Response } from './types'
5
6export async function Webhook_StripeCheckout_1_0_0(
7 body: Body,
8 { prisma }: DB,
9): Promise<Response> {
10 try {
11 const signature = body.headers?.['stripe-signature'] as string
12 const event = stripe.webhooks.constructEvent(body.raw, signature, process.env.STRIPE_WEBHOOK_SECRET)
13
14 if (event.type !== 'payment_intent.succeeded') {
15 return { success: true, statusCode: 200, action: 'ignored' }
16 }
17
18 // ...handle the event
19
20 return { success: true, statusCode: 200, action: 'processed' }
21 } catch (err: any) {
22 return { success: false, statusCode: 400 }
23 }
24}
25The 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.
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.
1
2{
3 "statusCode": 500,
4 "message": "Fail.. Query does not exist. Check the name and version",
5 "error": "Query does not exist",
6 "devMessages": [
7 "Cannot Parse the body.data obj into JSON. Check the object structure in the data obj: { 'data': {} }",
8 "Check the query name in the body JSON is correct. { 'query': 'Function_Name' }",
9 "CORE ONLY:: There are no other functions"
10 ]
11}
12