anflip

API & webhook documentation

Create flipbooks automatically from your own system with the anflip API, and get webhooks when a flipbook is ready, a conversion fails, or a new lead arrives.

Last updated September 29, 2026

The anflip API lets your own system (a CMS, an online store, an internal app) create and manage flipbooks without uploading through the dashboard. Webhooks go the other way: anflip tells your system when something happens.

The API is available on every plan. File size, page count, number of flipbooks, and storage follow the plan of the team that owns the API key.

When to use it

  • Catalogs that change often. Your store generates a price catalog PDF every week and sends it to anflip through the API. The flipbook link goes on your website automatically.
  • Regular publications. Magazines, newsletters, or monthly reports become flipbooks as soon as the final PDF is ready. The flipbook.ready webhook tells your system to publish the link.
  • Leads straight to sales. The lead.created webhook sends what readers type in a lead form to your CRM, WhatsApp, or Telegram within seconds.
  • Many clients. Agencies create and manage flipbooks for dozens of clients from their internal tools.

Quick start

  1. Open Dashboard → API, name the key (for example "Online store"), and click Create key. Copy the key: it is shown only once.
  2. Upload your first PDF:
curl -X POST https://manage.anflip.id/api/v1/ext/flipbooks \
  -H "Authorization: Bearer afl_xxxxxxxxxxxxxxxx" \
  -H "Accept: application/json" \
  -F "file=@catalog.pdf" \
  -F "title=October catalog" \
  -F "visibility=unlisted"
  1. Wait for the conversion with the flipbook.ready webhook or by checking GET /flipbooks/{id}, then share the public_url.

Authentication & general rules

Send the API key with every request:

Authorization: Bearer afl_xxxxxxxxxxxxxxxx
Accept: application/json
ItemValue
Base URLhttps://manage.anflip.id/api/v1/ext
FormatJSON, except file uploads (multipart/form-data)
Rate limit60 requests per minute per key. Above that you get 429
Error languageIndonesian. Add Accept-Language: en for English
KeysUp to 10 per team
OwnerFlipbooks are created in the key's team, on behalf of the member who created the key
Keep the API key on your server (an .env file or a secret manager). Never put it in code that runs in a browser or a mobile app, where anyone can read it. If a key leaks, delete it in Dashboard → API and create a new one.

The flipbook object

Every endpoint answers with flipbooks in this shape (inside data):

{
  "id": "01J9Z6K3Q4T8V2N5M7P0R1S3W4",
  "title": "October catalog",
  "slug": "5f2a9c01be",
  "status": "ready",
  "visibility": "unlisted",
  "page_count": 24,
  "progress": 100,
  "converting": false,
  "has_live_version": true,
  "has_password": false,
  "allow_download": false,
  "error": null,
  "cover_url": "https://cdn.anflip.id/fb/01J9…/v1/p1-md.webp",
  "public_url": "https://anflip.id/flip-book/5f2a9c01be",
  "storage_bytes": 5241880,
  "created_at": "2026-10-01T02:00:00+00:00",
  "processed_at": "2026-10-01T02:00:41+00:00",
  "published_at": "2026-10-01T02:00:00+00:00"
}
FieldMeaning
statusqueued, processing, ready, or failed
progressConversion progress, 0–100
has_live_versiontrue once there are pages to read. Use this, not status, to know a flipbook can be shared
convertingtrue while a conversion runs (also while a PDF is being replaced)
visibilityprivate (team only), unlisted (anyone with the link, not indexed by Google), public
public_urlThe link for readers
errorWhy it failed when status is failed, for example a damaged or password-protected PDF

Endpoints

Create a flipbook

POST /flipbooks as multipart/form-data. The conversion runs in the background, so the answer comes right away with status 202 and status: "queued".

FieldRequiredNotes
fileyesA PDF. The maximum size follows the team's plan
titlenoTitle. Defaults to the file name
visibilitynoprivate (default), unlisted, or public. For unlisted and public the key's creator must have verified their email

List flipbooks

GET /flipbooks returns the 50 newest flipbooks per page. Use ?page=2 for the next page. Paging details are in meta (current_page, last_page, total).

Get a flipbook

GET /flipbooks/{id} returns one flipbook. Handy for checking a conversion.

Delete a flipbook

DELETE /flipbooks/{id}. The flipbook's link stops working right away. Answer: { "data": { "id": "…", "deleted": true } }.

Waiting for the conversion

A 20–50 page PDF usually takes under a minute; hundreds of pages can take a few minutes. Two ways to wait:

  • The flipbook.ready webhook (recommended). No repeated checks: anflip tells you when it is done.
  • Polling. Check GET /flipbooks/{id} every 5–10 seconds until has_live_version is true or status is failed.
async function waitUntilReady(id) {
  for (;;) {
    const res = await fetch(`https://manage.anflip.id/api/v1/ext/flipbooks/${id}`, {
      headers: { Authorization: `Bearer ${process.env.ANFLIP_KEY}`, Accept: "application/json" },
    });
    const { data } = await res.json();
    if (data.has_live_version) return data.public_url;
    if (data.status === "failed") throw new Error(data.error);
    await new Promise((r) => setTimeout(r, 8000));
  }
}

Errors

StatusMeaningWhat to do
401Wrong, deleted, or missing API keyCheck the Authorization header
403Not allowed, for example publishing when the key's creator has not verified their emailRead message
404The flipbook does not exist or belongs to another teamCheck the id
422Invalid input: not a PDF, file too large, or the plan's quota is fullPer-field details are in errors
429More than 60 requests per minuteWait a moment and retry
5xxA problem on anflip's sideRetry a little later

Example 422 answer:

{
  "message": "Your plan allows 5 flipbooks. Delete old ones or upgrade your plan.",
  "errors": { "file": ["Your plan allows 5 flipbooks. Delete old ones or upgrade your plan."] }
}

Webhooks

Adding a webhook

In Dashboard → API → Webhook:

  1. Enter a URL on your system. It must be https:// and reachable from the internet (not localhost or a private network address).
  2. Pick the events you want, then click Add webhook.
  3. Copy the signing secret (starting with whsec_). It is shown only once and is used to verify deliveries.
  4. Send a ping to try it. The last delivery status shows in the webhook list.

Up to 5 webhooks per team.

Events

EventSent whendata contains
flipbook.readyA conversion finishes, also after a PDF is replacedThe full flipbook object
flipbook.failedA conversion failsid, title, error (always in English)
lead.createdA reader fills in a flipbook's lead formid, flipbook (id, title, url), data (the answers), created_at
pingYou press the ping button in the dashboardteam

lead.created answers only hold the fields the form uses: name, email, phone, company, and message.

Delivery format

anflip sends a POST with a JSON body like this:

{
  "id": "evt_01J9Z7A2B3C4D5E6F7G8H9J0K1",
  "event": "lead.created",
  "created_at": "2026-10-01T03:30:00+00:00",
  "data": {
    "id": 128,
    "flipbook": { "id": "01J9Z6K3Q4T8V2N5M7P0R1S3W4", "title": "October catalog", "url": "https://anflip.id/flip-book/5f2a9c01be" },
    "data": { "name": "Sari", "email": "sari@example.com", "phone": "081234567890" },
    "created_at": "2026-10-01T03:30:00+00:00"
  }
}

Headers sent along:

HeaderContent
Content-Typeapplication/json
X-Anflip-EventThe event name, for example lead.created
X-Anflip-Signaturesha256= followed by the HMAC-SHA256 of the raw body, using the signing secret
User-Agentanflip-webhooks/1.0

Verifying the signature

Always check the signature before processing, so forged deliveries are rejected. Compute the HMAC over the raw body (exactly as received, before parsing it as JSON).

// Node.js (Express): app.post("/anflip-webhook", express.raw({ type: "application/json" }), handler)
import crypto from "node:crypto";

function verify(rawBody, header, secret) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(header ?? "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// PHP / Laravel
$expected = 'sha256='.hash_hmac('sha256', $request->getContent(), env('ANFLIP_WEBHOOK_SECRET'));
abort_unless(hash_equals($expected, (string) $request->header('X-Anflip-Signature')), 401);
# Python (Flask)
import hashlib, hmac

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header or "")

Answering, retries, and good practice

  • Answer 2xx within 10 seconds. Store the delivery and process it in the background (a queue). Don't make anflip wait for long work.
  • Failed deliveries are retried (non-2xx, timeout, or no connection) up to 5 times in total, after 10 seconds, 1 minute, 5 minutes, and 30 minutes.
  • Expect duplicates. Because of retries the same event can arrive more than once. Store the event id (evt_…) and skip ones you already processed.
  • Order is not guaranteed. Use created_at when order matters.
  • Redirects are not followed. Register the final URL, not one that redirects.

Full example: a weekly catalog

A store creates catalog.pdf every Monday, sends it to anflip, and puts the link on its website once it is ready.

// Laravel: run by the scheduler every Monday
$response = Http::withToken(env('ANFLIP_KEY'))->acceptJson()
    ->attach('file', file_get_contents(storage_path('catalog.pdf')), 'catalog.pdf')
    ->post('https://manage.anflip.id/api/v1/ext/flipbooks', [
        'title' => 'Catalog '.now()->format('F j, Y'),
        'visibility' => 'unlisted',
    ])->throw();

Setting::put('catalog_pending', $response->json('data.id'));
// Webhook route: POST /anflip-webhook
$expected = 'sha256='.hash_hmac('sha256', $request->getContent(), env('ANFLIP_WEBHOOK_SECRET'));
abort_unless(hash_equals($expected, (string) $request->header('X-Anflip-Signature')), 401);

if ($request->input('event') === 'flipbook.ready'
    && $request->input('data.id') === Setting::get('catalog_pending')) {
    Setting::put('catalog_url', $request->input('data.public_url')); // shown on the website
}

return response()->noContent();

No code

  • Google Sheets and Mailchimp connect directly in Dashboard → Integrations, without the API.
  • Other apps (CRM, Slack, email, WhatsApp gateways): point a webhook at Zapier, Make, or n8n with a "Catch webhook" / "Webhooks" trigger, then pass it on.

Open API settings