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.
Webhook structure
Every event is delivered as a JSON body with the same envelope:| Field | Description |
|---|---|
id | A unique identifier (UUID) for this event delivery. |
created_at | When the event was created, as an ISO 8601 timestamp. |
category | What happened — one of the categories listed below. |
associated_object_type | The kind of resource the event is about: payment, disbursement, charge_authorization, bill_payment, check, or vendor. |
associated_object_id | The ID of that resource. Use it to fetch the current state from the API. |
type | Always the literal string event. |
Delivery and retries
Nickel sends each event as aPOST 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 toPOST /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:
tis the unix timestamp (in seconds) of the delivery attempt.v1is the hex-encoded HMAC-SHA256 of the string{t}.{raw request body}, keyed with your webhook secret.
- Split the header into
tandv1. - 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. - Compare your digest to
v1with a constant-time comparison. - Reject deliveries whose
tis too far from your current time (5 minutes is a reasonable tolerance) to guard against replayed requests.
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.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 showSUCCEEDED, 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 afterpayment.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 readrefunds[] 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 showsrefunds[].voided: true.
disbursement.pending
Nickel has batched settled payments into a disbursement headed for your bank account. Fetch the disbursement for the total and thepaymentIds 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 confirmactive: 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: checkactive 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 asPaid — 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 reportsstatus: "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 withGET /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 currentvendorDeliveryMethods[].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 aGET 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.
