FloPayFloPay
Guides

SDK Webhook Verification

Verify and handle Flo webhook signatures with the FloPay Node SDK.

SDK Webhook Verification

The @flopay/node SDK provides webhooks.constructEvent to verify Flo webhook signatures and parse event payloads.

Setup

You need:

  • A Flo webhook endpoint secret (flo_whsec_...)
  • The raw request body (as a string or Buffer -- not parsed JSON)
  • The Flo-Signature header from the incoming request

Next.js API Route Example

// app/api/webhooks/route.ts
import { FloPay } from '@flopay/node';
import { NextRequest, NextResponse } from 'next/server';
 
const flopay = new FloPay(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.FLO_WEBHOOK_SECRET!;
 
export async function POST(request: NextRequest) {
  const body = await request.text();
  const signature = request.headers.get('flo-signature');
 
  if (!signature) {
    return NextResponse.json({ error: 'Missing signature' }, { status: 400 });
  }
 
  let event;
  try {
    event = flopay.webhooks.constructEvent(body, signature, webhookSecret);
  } catch (err) {
    console.error('Webhook signature verification failed:', err);
    return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
  }
 
  switch (event.type) {
    case 'checkout.session.completed':
      const session = event.data.object;
      // Fulfill the order
      console.log('Checkout completed:', session.id);
      break;
 
    case 'payment_intent.succeeded':
      const paymentIntent = event.data.object;
      console.log('Payment succeeded:', paymentIntent.id);
      break;
 
    case 'payment_intent.payment_failed':
      const failedIntent = event.data.object;
      console.error('Payment failed:', failedIntent.id);
      break;
 
    default:
      console.log('Unhandled event type:', event.type);
  }
 
  return NextResponse.json({ received: true });
}

You must read the request body as raw text (request.text()), not parsed JSON. Signature verification requires the exact bytes that were sent. If your framework parses the body automatically, configure it to skip parsing for your webhook route.

Express Example

import express from 'express';
import { FloPay } from '@flopay/node';
 
const app = express();
const flopay = new FloPay(process.env.STRIPE_SECRET_KEY!);
 
// Use raw body parser for webhook route
app.post(
  '/api/webhooks',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['flo-signature'] as string;
 
    try {
      const event = flopay.webhooks.constructEvent(
        req.body,
        signature,
        process.env.FLO_WEBHOOK_SECRET!,
      );
 
      // Handle event...
      console.log('Received:', event.type);
      res.json({ received: true });
    } catch (err) {
      console.error('Webhook error:', err);
      res.status(400).send('Webhook Error');
    }
  },
);

Testing Locally

Use a local tunnel such as ngrok and point your Flo webhook endpoint at it:

ngrok http 3000

Use the webhook signing secret returned when you create the endpoint and verify the incoming Flo-Signature header against the raw request body.

On this page