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: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. A repeated delivery of an event keeps its id, so skip an id you’ve already processed. Distinct events always have distinct ids, even for the same object: a payment refunded in two parts sends two payment.refunded events, and deduplicating by id keeps both. 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.succeeded follows when it is.
payment.succeeded
A customer’s payment completed. Fetch the payment: it now showsSUCCEEDED.
For a card payment this arrives together with payment.made. For a bank (ACH) payment it arrives when the debit settles, usually a few business days after payment.made. Listen for it instead of polling a PENDING payment for its status to change.
SUCCEEDED is not irreversible: the customer’s bank can still return an ACH debit for up to 60 days, which sends payment.failed, and a customer can dispute a card payment, which sends payment.disputed. To follow the money into your own account, use the disbursement.* events.
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.
payment.disputed
Your customer disputed a card payment with their card issuer. This is a chargeback: the money goes back to the customer while the dispute is decided. Fetch the payment: it now showsFAILED, and the invoice it paid reopens to ACTIVE.
Treat the money as not yours. Reverse whatever you recorded when the payment was made — the funds will not arrive, or will be clawed back if already paid out. If the dispute is a mistake, ask your customer to pay the invoice again; for questions about the dispute, contact support@nickel.com.
If the dispute is decided in your favor, payment.dispute_won follows. If it’s decided against you, nothing more is sent: the payment stays FAILED.
payment.dispute_won
A disputed card payment was decided in your favor, and the money is yours again. Fetch the payment: it showsSUCCEEDED again, and the invoice it paid counts the payment again.
Re-record the payment you reversed on payment.disputed. If your customer paid the invoice again while the dispute was open, both payments now count toward the invoice — check it, and refund the extra payment if you don’t mean to keep it.
Nickel restores the payment automatically only when nothing else, such as a refund, has changed it since the dispute opened. When it can’t, this event isn’t sent and the payment stays FAILED.
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, by you, or by Nickel when the customer’s bank returned a debit because the account can’t be debited at all (for example, it’s closed or the account number is invalid). A returned account can’t be charged again, so the customer has to put a new payment method on file. 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.check_deposit.deposited
A check you deposited into Nickel Banking settled, and the money is yours to spend. Thecheck.* events above are about checks Nickel mails to your vendors; check_deposit.* events are about checks you deposit. Fetch the deposit with GET /nickelBanking/checkDeposits/{checkDepositId}: its status is DEPOSITED and depositedAt says when it settled.
The bank can still reverse a deposited check, sometimes days later. That arrives as check_deposit.returned.
check_deposit.rejected
The bank refused a check you deposited, so it never funded: for example, it couldn’t read the photos, or the payee doesn’t match the account. Fetch the deposit forrejectionReason, then deposit the check again or ask your customer for a new one.
Nickel voids the deposit’s applications, which reopens the invoices it paid. The void can land a moment after this event, so an application may briefly still read voided: false. Once voided, its voidReason is DEPOSIT_REJECTED.
check_deposit.returned
The bank reversed a check after crediting it, for example because the payer’s account didn’t have the funds or they stopped payment, and the amount has left your account again. Fetch the deposit forreturnReason, and reverse whatever you recorded when it was deposited.
As with a rejection, Nickel voids the deposit’s applications and the invoices reopen, with voidReason DEPOSIT_RETURNED.
The event is sent to your organization’s webhook whoever registered it, but fetching a check deposit needs a key whose team member can see Nickel Banking deposits. Without that permission the fetch answers
403 and says which permission is missing.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.
