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.
- 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.
- 02
Save your backend destination
Paste the URL of the backend that should receive each forwarded webhook into Ackflow settings.
- 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.
- Retry 01
1 minute
after the previous failed attempt
- Retry 02
5 minutes
after the previous failed attempt
- Retry 03
30 minutes
after the previous failed attempt
- Retry 04
2 hours
after the previous failed attempt
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.