Stripe webhook

  1. Using webhooks

    First search for stripe cli, find the relevant version, download and unzip it. Select the x86_64 version.

    Enter cmd in the folder where the unzipped file is located, then enter stripe login to log in. After jumping to the stripe web page and logging in, go back to cmd and enter stripe listen --forward-to localhost:8000/v1/webhook . At this time, you can get the siging secret, which is valid for 90 days. E.g:

    1
    whsec_9fd569e590734157a0d8xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    Then fill this key into the .env of the backend.


  1. Update data via webhook

    First of all, the webhook collects data through events. The webhook will continuously monitor the interface. When the payment-related behavior occurs, such as ready to pay, paid, etc., the webhook will receive events. Because we currently only need to update the database after a payment occurs, we need to filter the events to only fire the callback function after the payment.

    The updated data comes from the webhook listener. But we still need the id of the database document to update the existing data. This id exists before the payment action, so it can be transmitted through the metadata field. When creating the Stripe session, we have passed the id to Stripe via metadata. So when updating the data, we can just take the data from metadata.

    metadata cannot store complex nested objects, so we only pass in the id, and other data can be stored in the database.


  1. webhook listening event

    Whenever an event such as payment intend, payment success, payment failure occurs, the webhook can listen to the event. Therefore, we need to filter the events, and then call the callback function after the event occurs to process the data in the next step.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    async function webhookHandler(req, res) {
    const sig = req.headers["stripe-signature"];

    let event;
    try {
    event = stripeAPI.webhooks.constructEvent(req.body, sig, endpointSecret);
    } catch (err) {
    console.log('ERROR', err.message);
    return res.status(400).send(`Webhook error: ${err.message}`);
    }

    if (event.type === 'checkout.session.completed') {
    const session = event.data.object;
    return fulfillOrder(session)
    .then(() => res.status(200))
    .catch((err) => res.status(400).send(`Webhook Error: ${err.message}`));
    }
    }

    const fulfillOrder = async (session) => {
    const order = await Order.findById(session.metadata.orderId).exec();
    ...
    }

  2. Set routes in JSON raw format

    We cannot use webhooks without setting up routes in JSON raw format.


    For express.js:

    1
    app.use('/v1/webhook', express.raw({type: "*/*"}))

    For nest.js:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //src/app.module.ts
    import {
    applyRawBodyOnlyTo,
    JsonBodyMiddleware,
    RawBodyMiddleware,
    } from "@golevelup/nestjs-webhooks";

    export class AppModule implements NestModule {
    // Apply raw body parsing to the routes with path "stripe/webhook",
    // and then to automatically apply JSON body parsing to all other routes
    // with the exclusion of the raw routes.
    configure(consumer: MiddlewareConsumer) {
    applyRawBodyOnlyTo(consumer, {
    method: RequestMethod.ALL,
    // path should be the same as webhookConfig controllerPrefix (settled in stripe module)
    path: "stripe/webhook",
    });
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import { MongoExceptionFilter } from "./common/filters/mongoose-exception.filter";

    async function bootstrap() {
    //bodyParser setting
    const app = await NestFactory.create<NestExpressApplication>(AppModule, {
    bodyParser: false,
    });
    app.enableCors();
    app.set("trust proxy", 1);
    app.useGlobalPipes()

  1. Flowchart


Share