Skip to main content
Webhooks are the primary way that Nickel tells your application that something has happened. To receive the event notifications documented on this page, create a webhook subscription programmatically via the POST /webhook endpoint. Once you specify a webhook URL, Nickel sends an HTTP POST request to that URL whenever something happens against your Nickel account.
Your account has a single API webhook subscription: creating a webhook replaces the API webhook already registered on your account, if any. A legacy payment-alert webhook configured from the dashboard settings page is separate and is left in place. To receive the events documented here, create your webhook via POST /webhook.

Webhook structure

Every event is delivered as a JSON body with the same envelope:
{
  "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
  "created_at": "2026-01-31T23:59:59.000Z",
  "category": "disbursement.pending",
  "associated_object_type": "disbursement",
  "associated_object_id": "clrv6k8y80003wq02x36efjnv",
  "type": "event"
}
FieldDescription
idA unique identifier (UUID) for this event delivery.
created_atWhen the event was created, as an ISO 8601 timestamp.
categoryWhat happened — one of the categories listed below.
associated_object_typeThe kind of resource the event is about: payment, disbursement, charge_authorization, bill_payment, check, or vendor.
associated_object_idThe ID of that resource. Use it to fetch the current state from the API.
typeAlways the literal string event.

Delivery and retries

Nickel sends each event as a POST request with Content-Type: application/json and Nickel-Signature headers. Respond with any 2xx status code to acknowledge receipt. If your endpoint responds with a non-2xx status code or is unreachable, Nickel retries the delivery with exponential backoff (roughly 3s, 6s, 12s, and 24s between attempts) for up to 5 total attempts. Deliveries can occasionally arrive more than once or out of order, so make your handler idempotent — the event id is stable across retries of the same delivery. The signature is computed per attempt, so the t timestamp in the Nickel-Signature header reflects when that attempt was sent.

Verifying signatures

The response to POST /webhook includes a secret — a whsec_-prefixed string that Nickel uses to sign every delivery to that webhook. Store it alongside your API key; you can also retrieve it later with GET /webhook. Each delivery carries a Nickel-Signature header:
Nickel-Signature: t=1751850000,v1=4c47623a7e2f61be96a55eb60ea48a1dc105e3f38e0e78f4b788b6a2ac0f2c6b
  • t is the unix timestamp (in seconds) of the delivery attempt.
  • v1 is the hex-encoded HMAC-SHA256 of the string {t}.{raw request body}, keyed with your webhook secret.
To verify a delivery:
  1. Split the header into t and v1.
  2. Compute HMAC-SHA256 over {t}.{body} with your secret, using the raw request body bytes — don’t parse and re-serialize the JSON first, since any formatting difference changes the digest.
  3. Compare your digest to v1 with a constant-time comparison.
  4. Reject deliveries whose t is too far from your current time (5 minutes is a reasonable tolerance) to guard against replayed requests.
import { createHmac, timingSafeEqual } from 'crypto'

function verifyNickelSignature(
  header: string | undefined,
  rawBody: string,
  secret: string,
  toleranceSeconds = 300,
): boolean {
  const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? '')
  if (!match) return false

  const [, timestamp, signature] = match
  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex')

  return (
    timingSafeEqual(Buffer.from(signature), Buffer.from(expected)) &&
    Math.abs(Date.now() / 1000 - Number(timestamp)) <= toleranceSeconds
  )
}
Most web frameworks parse JSON bodies before your handler runs, so you’ll need the raw body captured separately — for example express.json({ verify: (req, _res, buf) => { req.rawBody = buf.toString() } }) in Express.
Webhooks created before signing shipped have no secret (GET /webhook returns "secret": null) and their deliveries are unsigned. Recreate the webhook with POST /webhook to get a secret — the new subscription replaces the old API webhook at the same or a new URL.
As defense in depth, treat the webhook purely as a trigger: fetch the associated object from the API (see Consuming Events) and act on that response rather than on the delivery payload itself.

Categories

Nickel currently supports the following webhook notifications. Each entry says what changed and what your application should do about it — always by fetching the associated object, not by acting on the event alone.

payment.made

A customer paid an invoice. Fetch the payment before recording anything: for a card payment it will show SUCCEEDED, but for a bank (ACH) payment this event fires when the debit is initiated, so the payment will still be PENDING — don’t treat the money as final yet.

payment.failed

A payment failed — most often the customer’s bank returned an ACH debit, which can happen up to 60 days after payment.made. Fetch the payment and reverse whatever you recorded when it was made; the funds will not arrive (or will be clawed back if already paid out).

payment.refunded

You refunded a settled payment. Fetch the payment and read refunds[] for the amount. A fully refunded payment reopens its invoice to ACTIVE, so the customer can pay again — delete the invoice if you don’t want that.

payment.voided

A refund was requested before the charge settled, so Nickel cancelled the original charge outright — no money ever moved and no processing fee applies. The fetched payment shows refunds[].voided: true.

disbursement.pending

Nickel has batched settled payments into a disbursement headed for your bank account. Fetch the disbursement for the total and the paymentIds it contains — this is the moment to start reconciling against your bank statement.

disbursement.succeeded

The receiving bank accepted the disbursement. Depending on your bank’s turnaround it may not appear in your account balance yet — match it to the deposit when it does.

charge_authorization.authorized

Your customer authorized you to charge a payment method they put on file. Fetch the charge authorization and confirm active: true — you can now charge their open invoices against it.

charge_authorization.deleted

An authorization was revoked, by the customer or by you. Stop charging it: check active on the fetched authorization before every charge rather than caching yesterday’s answer.

bill_payment.succeeded

Your vendor received the funds. The bill it paid now counts as Paid — safe to record in your books.

bill_payment.failed

A bill payment failed — for example, the debit from your funding source was returned. The bill is still owed; fix the funding source or delivery method and pay it again.

bill_payment.cancelled

A bill payment was cancelled before it went out to the payment network. Note that the fetched payment reports status: "FAILED" — this event is how you tell a cancellation apart from a genuine failure.

check.sent

Nickel mailed the check to the vendor. Check events carry the bill payment’s ID — fetch it with GET /billPayment/{billPaymentId}.

check.delivered

The check arrived at the vendor’s mailing address. Nothing to do — the vendor still needs to deposit it.

check.deposited

The vendor deposited the check. The bill payment completes from here.

check.returned

The check came back undeliverable. Confirm the vendor’s mailing address (their check delivery method) and pay the bill again.

check.failed

The check payment failed. Fetch the bill payment for its state, then retry with a corrected delivery method.

vendor.payout_method_updated

A vendor’s delivery method was created or changed — through the onboarding form or by you. Refetch the vendor and use the current vendorDeliveryMethods[].id when paying; a cached ID may now point at stale details.

Consuming events

Individual events don’t contain very much information on their own. This is by design, as the API structure can remain extremely stable and avoid difficult webhook migrations in the future as the Nickel API changes. If you need additional metadata, such as the amount of the disbursement in the envelope example above, make a GET request to the API for that information. You can use the associated_object_type or category fields to determine what resource to fetch from the API.

See it in practice

Receive webhooks

Register an endpoint, build a reliable receiver, and test the loop in the sandbox.