Tidepay API Docs

Webhooks

Receiving and verifying signed event notifications.

Tidepay sends signed HTTP POST requests to your configured webhook URL whenever a subscription or payment event happens, so you don't have to poll the API for state changes.

Configuring your endpoint

Set your webhook URL from the dashboard: Settings → Business, under "Webhook URL". Your Signing Secret (Settings → API keys) is generated automatically the first time you save a webhook URL.

One secret for both environments

Unlike API keys, there is a single Signing Secret shared by both sandbox and live events — it doesn't come in test/live pairs. Use the payload's isSandbox field (see below) to tell them apart if you point both environments at the same endpoint.

Events

EventFired when
subscription.createdA subscription is created, before the subscriber has approved an allowance
subscription.activeThe first charge on a subscription settles successfully
subscription.canceledA subscription is canceled
payment.succeededAny charge (recurring or one-off) settles successfully
payment.failedA charge fails (insufficient allowance/balance, a reverted transaction, etc.)
customer.createdA customer record is created
customer.updatedA customer record is updated
customer.deletedA customer record is deleted

Payload shape

Every webhook body has the same envelope:

{
  "id": "evt_...",
  "event": "payment.succeeded",
  "createdAt": "2026-08-03T14:00:00.000Z",
  "isSandbox": false,
  "data": {
    "subscriptionId": "sub_...",
    "chargeId": "chg_..."
  }
}

data varies by event — it carries the relevant resource IDs (subscription, charge, customer, etc.) needed to look up the full object via the API, rather than duplicating the entire resource inline.

Verifying the signature

Each request includes two headers:

X-Tidepay-Timestamp: 1735689600
X-Tidepay-Signature: 5f4dcc3b5aa765d61d8327deb882cf99...

The signature is an HMAC-SHA256 of the timestamp and the raw request body (before any JSON parsing), signed with your Signing Secret:

signature = HMAC-SHA256(webhookSecret, `${timestamp}.${rawBody}`)

Recompute it on your end and compare using a constant-time comparison — never === on the raw strings, which leaks timing information an attacker can use to guess the signature byte by byte.

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

function isValidSignature(
  secret: string,
  timestamp: string,
  rawBody: string,
  signature: string,
): boolean {
  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && timingSafeEqual(a, b);
}

Read the raw body

Your HTTP framework's JSON body parser typically re-serializes the parsed object, which can silently change field order or spacing and break the signature check. Verify against the exact bytes Tidepay sent — read the raw body before parsing it as JSON, or configure your framework to expose it separately (e.g. Express's express.raw(), or reading the Request stream directly in a Next.js route handler).

Retries

If your endpoint doesn't respond with a 2xx status (or the request fails outright — timeout, connection refused, etc.), Tidepay retries with exponential backoff: 1, 5, 30, 120, and 360 minutes after the previous attempt, for up to 6 total attempts. After the last attempt fails, the delivery is marked failed and not retried again.

Handle deliveries idempotently — use the payload's id field to detect and ignore duplicates, since a retry can occur even after your endpoint successfully processed the event but the response was lost in transit.

Rotating your Signing Secret

If your secret is ever exposed (e.g. committed to a public repo, logged somewhere insecure), rotate it from Settings → API keys → "Rotate signing secret". This immediately invalidates the old secret — every subsequent webhook is signed with the new one, so update your endpoint's verification code first if you want zero missed/rejected deliveries during the switch.

Sandbox webhooks

Sandbox events use the exact same signature scheme as live events — no special verification logic needed. See Sandbox mode for how sandbox and live events are distinguished.

On this page