Guide
Receive webhooks
Subscribe an endpoint to reader events, verify that each delivery genuinely came from Flipbooker, and debug the ones that never arrive.
Before you start
- An API key with the
webhooks:readandwebhooks:writescopes. - A publicly reachable HTTPS endpoint that responds with a 2xx status quickly.
- 1
Choose the events you care about
Nine event types are available. Subscribe only to what you will act on — every extra event is another delivery your endpoint has to absorb.
access_requested,access_granted,access_denied,flipbook_opened,page_dwelled,link_clicked,download_requested,cta_submittedandsession_ended. - 2
Create the subscription
Set a
signing_secretat creation — without one you cannot verify that a delivery came from Flipbooker. Generate a long random string and store it alongside your API key.Narrow the firehose with
filters: restrict to specificbook_ids,tracked_link_idsorrecipient_emails. Every filtered flipbook must belong to your workspace.POST /webhook-subscriptionscurl -X POST https://app.flipbooker.com/api/v1/webhook-subscriptions \ -H "Authorization: Bearer $FLIPBOOKER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "CRM sync", "provider": "generic", "target_url": "https://example.com/hooks/flipbooker", "signing_secret": "whsec_...", "event_types": ["flipbook_opened", "cta_submitted", "session_ended"], "filters": { "book_ids": [4821] } }'provider must be generic or zapierUse "generic" for your own endpoint. "zapier" is reserved for Zapier's own subscription handshake. - 3
Verify the signature on every delivery
Each delivery carries
X-Webhook-Signature,X-Webhook-TimestampandX-Webhook-Event. The signature is an HMAC-SHA256 of"{timestamp}.{raw body}", keyed with your signing secret.Compute it over the raw request body — do not parse and re-serialise the JSON first, or the bytes will differ and the signature will never match. Compare with a constant-time function, and reject timestamps more than 5 minutes old to block replays.
Express exampleimport crypto from 'node:crypto' // Give this route the RAW body, not a parsed one. app.post('/hooks/flipbooker', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.get('X-Webhook-Signature') ?? '' const timestamp = Number(req.get('X-Webhook-Timestamp') ?? 0) // Reject anything outside Flipbooker's 5 minute tolerance. if (Math.abs(Date.now() / 1000 - timestamp) > 300) { return res.status(400).send('stale') } const expected = crypto .createHmac('sha256', process.env.FLIPBOOKER_WEBHOOK_SECRET) .update(`${timestamp}.${req.body.toString('utf8')}`) .digest('hex') // timingSafeEqual throws on a length mismatch, so check that first. if (signature.length !== expected.length) { return res.status(401).send('bad signature') } const ok = crypto.timingSafeEqual( Buffer.from(expected, 'hex'), Buffer.from(signature, 'hex') ) if (!ok) return res.status(401).send('bad signature') // Acknowledge fast, then do the slow work out of band. res.sendStatus(202) queue.push(JSON.parse(req.body)) } ) - 4
Send yourself a test delivery
Trigger a real delivery against the subscription to confirm your endpoint accepts it. A
202means Flipbooker queued it.You can also fire an ad-hoc sample without a subscription using
POST /webhooks, which posts a sample payload to any URL you name.POST /webhook-subscriptions/{subscription}/testcurl -X POST https://app.flipbooker.com/api/v1/webhook-subscriptions/$SUBSCRIPTION_ID/test \ -H "Authorization: Bearer $FLIPBOOKER_API_KEY" - 5
Debug what went wrong
When events stop arriving, read the delivery history before changing anything. It records each attempt with the status your endpoint returned, so you can tell a Flipbooker problem from a your-server problem.
A delivery that never got a response usually means your endpoint took too long — acknowledge within a few seconds and process asynchronously.
GET /webhook-subscriptions/{subscription}/deliveriescurl https://app.flipbooker.com/api/v1/webhook-subscriptions/$SUBSCRIPTION_ID/deliveries \ -H "Authorization: Bearer $FLIPBOOKER_API_KEY"