Ackflow developer docs

Move webhooks through a calmer middle layer.

Ackflow accepts webhooks, validates and persists them, forwards them to your configured backend, and makes delivery failures replayable. Use this guide to connect Stripe or Shopify in a few deliberate steps.

Start here

Quickstart

Get from provider to backend with one durable endpoint between them. Ackflow keeps the provider acknowledgement fast while delivery work continues in the background.

  1. 01

    Create an Ackflow project

    Create a project in Ackflow and obtain your ingest URL: https://www.ackflow.dev/api/ingest/{token}. The setup flow displays and copies this token-bearing URL, but the token cannot be recovered after you leave the creation result.

  2. 02

    Save your backend destination

    Paste the URL of the backend that should receive each forwarded webhook into Ackflow settings.

  3. 03

    Point your provider at Ackflow

    Set the Stripe or Shopify webhook destination to your Ackflow ingest URL so Ackflow can accept, persist, and deliver each event.

Ingest URL format: https://www.ackflow.dev/api/ingest/{token}

Receive JSON

Next.js App Router

Add a standard App Router route at app/api/webhooks/route.ts. The configured Ackflow destination receives the forwarded JSON payload and can return its normal HTTP response.

app/api/webhooks/route.ts

TypeScript example

import { NextRequest } from 'next/server';

export async function POST(request: NextRequest) {
  const payload: unknown = await request.json();

  return Response.json({
    received: true,
    payload,
  });
}

Receive JSON

Node.js / Express

Express parses the forwarded JSON with express.json(). Your configured destination receives the JSON payload along with relevant forwarded headers from Ackflow.

server.js

JavaScript example

import express from 'express';

const app = express();
app.use(express.json());

app.post('/webhooks/ackflow', (req, res) => {
  const payload = req.body;

  res.status(200).json({
    received: true,
    payload,
  });
});

app.listen(3000);

Verify before handling

Verifying Stripe Signatures

When project verification is enabled, Ackflow validates the exact raw body and Stripe-Signature at the proxy edge before forwarding. That prevents forged payloads from reaching your backend. Your own endpoint must still verify the raw body before JSON parsing; once the raw bytes are discarded, a parsed JSON body cannot be used for the same verification.

In Express, place the raw-body middleware on the Stripe route before express.json() parses the request. The signature covers the exact bytes received, not a re-serialized version of req.body.

server.js

JavaScript example

import crypto from 'node:crypto';
import express from 'express';

const app = express();
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {
  const signatureHeader = req.get('Stripe-Signature');
  const timestamp = signatureHeader?.match(/(?:^|,)t=(\d+)/)?.[1];
  const signature = signatureHeader?.match(/(?:^|,)v1=([a-f0-9]+)/)?.[1];

  if (!endpointSecret || !timestamp || !signature) {
    return res.status(400).json({ error: 'Invalid Stripe signature' });
  }

  const signedPayload = `${timestamp}.${req.body.toString('utf8')}`;
  const expected = crypto
    .createHmac('sha256', endpointSecret)
    .update(signedPayload)
    .digest('hex');
  const signaturesMatch =
    signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));

  if (!signaturesMatch) {
    return res.status(400).json({ error: 'Invalid Stripe signature' });
  }

  const payload: unknown = JSON.parse(req.body.toString('utf8'));
  return res.status(200).json({ received: true, payload });
});

app.listen(3000);

Recover without guesswork

Failure Retries

Delivery failures include non-2xx responses and network or timeout failures. Ackflow records each attempt, and the current worker allows four retries after the initial attempt.

  1. Retry 01

    1 minute

    after the previous failed attempt

  2. Retry 02

    5 minutes

    after the previous failed attempt

  3. Retry 03

    30 minutes

    after the previous failed attempt

  4. Retry 04

    2 hours

    after the previous failed attempt

01

Replay a preserved event

To replay an event, open a retained failed or delivered event in the dashboard and click the one-click Replay action. Ackflow queues the preserved event for delivery again, keeps its existing attempt timeline, and shows the new delivery attempt after the worker runs. Replay requires a configured destination, is owner-authenticated, and reports a conflict when the event is already pending or retrying instead of silently duplicating it.