Docs

Webhooks

Receive tip.created events and verify the timestamped HMAC-SHA256 signature over the raw request body.

Receive events

The app needs an HTTPS Webhook URL and the grant needs events.subscribe. When the creator receives a completed tip, Furipay POSTs this payload:

{
  "event": "tip.created",
  "name": "Ploy",
  "amount": 50,
  "message": "Keep going!",
  "createdAt": "2026-08-14T09:12:00.000Z"
}

Verify the signature

Every request carries X-Furipay-Signature in the form t=UNIX_SECONDS,v1=HEX_SIGNATURE. The signature is HMAC-SHA256 of t.rawBody, keyed by the webhook secret.

Capture the unchanged raw body before parsing JSON. Do not re-stringify the parsed object for verification:

import { createHmac, timingSafeEqual } from "node:crypto";

function verifyFuripayWebhook(
  rawBody: Buffer,
  header: string,
  webhookSecret: string,
): boolean {
  const fields = Object.fromEntries(
    header.split(",").map((part) => part.split("=", 2)),
  );
  const timestamp = Number(fields.t);
  const provided = Buffer.from(fields.v1 ?? "", "hex");

  if (
    !Number.isInteger(timestamp) ||
    Math.abs(Date.now() / 1000 - timestamp) > 300 ||
    provided.length !== 32
  ) {
    return false;
  }

  const expected = createHmac("sha256", webhookSecret)
    .update(String(timestamp) + ".")
    .update(rawBody)
    .digest();

  return timingSafeEqual(expected, provided);
}

Rejecting timestamps older than five minutes reduces replay risk. Then return HTTP 2xx quickly and perform heavier work in your own queue.

Retries and testing

  • Furipay gives each request ten seconds.
  • Network errors, timeouts, and non-2xx responses are attempted up to four times, waiting 0, 5, 60, and 600 seconds before each attempt.
  • The Developer Portal's test event makes one attempt and records the result in the Delivery log.
  • Make the endpoint safe to process more than once because a retry can repeat a request that already completed.
Webhooks | Furipay Docs