> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pocketsflow.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook examples

> Ready-to-adapt code for verifying and handling Pocketsflow webhooks in Node, Python, and PHP.

These handlers verify the `X-Pocketsflow-Signature`, acknowledge quickly, and
route on the `X-Pocketsflow-Event` header. Adapt them — add persistence, a job
queue, and real error handling for production. For the mechanics see
[Authentication & security](/api-webhooks/authentication-and-security) and
[Consuming webhooks](/api-webhooks/consuming-webhooks).

<Warning>
  Verify against the **raw** request body. The signed body includes a
  `webhookId` field, so re-serializing parsed JSON can change the bytes and break
  the signature.
</Warning>

## Node.js (Express)

```javascript theme={null}
import express from "express";
import crypto from "crypto";

const app = express();
const SECRET = process.env.POCKETSFLOW_WEBHOOK_SECRET;

app.post(
  "/webhooks/pocketsflow",
  express.raw({ type: "application/json" }), // keep the raw Buffer
  (req, res) => {
    const signature = req.get("X-Pocketsflow-Signature") || "";
    const eventType = req.get("X-Pocketsflow-Event") || "";
    const rawBody = req.body; // Buffer

    if (!verify(rawBody, signature, SECRET)) {
      return res.status(400).send("Invalid signature");
    }

    // Acknowledge first, then process asynchronously.
    res.status(200).send("OK");

    const event = JSON.parse(rawBody.toString("utf8"));
    queue.push({ eventType, event }); // hand off to a background worker
  }
);

function verify(rawBody, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  const a = Buffer.from(signature, "utf8");
  const b = Buffer.from(expected, "utf8");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Worker (illustrative) — idempotent routing by event type.
function handle({ eventType, event }) {
  switch (eventType) {
    case "order.completed":
      if (seen(event.order.id)) return;
      grantAccess(event.customer.email, event.product.id);
      markSeen(event.order.id);
      break;
    case "customer.subscription.created": // new subscriber — fires once, on activation
      provisionMembership(event.subscription.id, event.customer.email);
      break;
    case "invoice.payment_succeeded": // renewal
      if (seen(event.paymentId)) return;
      extendMembership(event.subscription.customerId);
      markSeen(event.paymentId);
      break;
    case "invoice.upcoming": // renewal is 3 days out — optional heads-up
      remindUpcomingCharge(event.subscription.customerId, event.renewalAt);
      break;
    case "invoice.payment_failed":
      startDunning(event.subscription.customerId, event.failureMessage);
      break;
    case "customer.subscription.pause": // billing stopped, access retained
      keepAccessNoBilling(event.subscription.customerId);
      break;
    case "customer.subscription.resumed": // billing restarted
      extendMembership(event.subscription.customerId);
      break;
    case "customer.subscription.deleted":
      revokeAccess(event.subscription.customerId);
      break;
    default:
      // Ignore events you don't handle.
      break;
  }
}
```

## Next.js (App Router route handler)

```typescript theme={null}
import crypto from "crypto";

export async function POST(req: Request) {
  const raw = Buffer.from(await req.arrayBuffer());
  const signature = req.headers.get("x-pocketsflow-signature") ?? "";
  const eventType = req.headers.get("x-pocketsflow-event") ?? "";
  const secret = process.env.POCKETSFLOW_WEBHOOK_SECRET!;

  const expected = crypto.createHmac("sha256", secret).update(raw).digest("hex");
  const ok =
    signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  if (!ok) return new Response("Invalid signature", { status: 400 });

  const event = JSON.parse(raw.toString("utf8"));
  await enqueue(eventType, event); // don't block the response
  return new Response("OK", { status: 200 });
}
```

## Python (Flask)

```python theme={null}
import hmac, hashlib, os
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["POCKETSFLOW_WEBHOOK_SECRET"].encode()

@app.post("/webhooks/pocketsflow")
def pocketsflow_webhook():
    raw = request.get_data()  # raw bytes, before parsing
    signature = request.headers.get("X-Pocketsflow-Signature", "")
    event_type = request.headers.get("X-Pocketsflow-Event", "")

    expected = hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(signature, expected):
        abort(400, "Invalid signature")

    event = request.get_json()
    enqueue(event_type, event)  # process in the background
    return "OK", 200
```

## PHP

```php theme={null}
<?php
$raw = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_POCKETSFLOW_SIGNATURE'] ?? '';
$eventType = $_SERVER['HTTP_X_POCKETSFLOW_EVENT'] ?? '';
$secret = getenv('POCKETSFLOW_WEBHOOK_SECRET');

$expected = hash_hmac('sha256', $raw, $secret);
if (!hash_equals($expected, $signature)) {
    http_response_code(400);
    exit('Invalid signature');
}

$event = json_decode($raw, true);
enqueue($eventType, $event); // process asynchronously
http_response_code(200);
echo 'OK';
```

## Testing your endpoint

Send a sample delivery any time with
[`POST /webhooks/{id}/test`](/api-reference/introduction#webhooks) — it signs and
delivers a realistic payload for the endpoint's first subscribed event, so you
can confirm signature verification and routing end to end before going live.

## Example repositories

Two complete, runnable examples on GitHub — clone one, drop in your IDs and
signing secret, and you have a working checkout embed plus a
signature-verifying webhook receiver.

<CardGroup cols={2}>
  <Card title="Subscriptions example" icon="github" href="https://github.com/pocketsflow/subscriptions-example">
    Embed a subscription checkout and handle `customer.subscription.*`,
    `invoice.*`, and `payment_intent.*` webhooks.
  </Card>

  <Card title="One-time products example" icon="github" href="https://github.com/pocketsflow/one-time-products-example">
    Embed a product checkout and handle `order.completed`, `order.refunded`,
    and `customer.created` webhooks.
  </Card>
</CardGroup>

## Related topics

* [Subscriptions example repo](https://github.com/pocketsflow/subscriptions-example) — runnable embed + webhook code
* [One-time products example repo](https://github.com/pocketsflow/one-time-products-example) — runnable embed + webhook code
* [Authentication & security](/api-webhooks/authentication-and-security)
* [Consuming webhooks](/api-webhooks/consuming-webhooks)
* [Webhook events](/api-webhooks/events)
