> ## Documentation Index
> Fetch the complete documentation index at: https://dev.nickel.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Receive Webhooks

> Register an endpoint, handle event deliveries reliably, and test the whole loop in the sandbox

Webhooks tell your application that something happened — a payment landed, a payout went out, a bill payment failed — without you polling for changes. This guide sets up a working receiver. For the full event catalog, payload reference, and delivery/retry semantics, see [Webhook Events](/concepts/webhooks).

<Info>
  **Before you start**: You need an HTTPS endpoint that Nickel can reach. For sandbox testing, a [webhook.site](https://webhook.site) inbox or an [ngrok](https://ngrok.com) tunnel to your laptop works great.
</Info>

<Steps>
  <Step title="Register your endpoint">
    Create the subscription with [`POST /webhook`](/api-reference/webhook/create-a-webhook):

    ```bash theme={null}
    curl -X POST https://rest.staging.nickel.com/webhook \
      -H "Authorization: Bearer $NICKEL_SANDBOX_KEY" \
      -H "Content-Type: application/json" \
      -d '{"url": "https://example.com/webhooks/nickel/f3f9a2c81b7d"}'
    ```

    ```json theme={null}
    {
      "id": "cmra19w22001msf02nuk4wo9m",
      "url": "https://example.com/webhooks/nickel/f3f9a2c81b7d",
      "secret": "whsec_your_webhook_signing_secret"
    }
    ```

    Store the `secret` alongside your API key — Nickel signs every delivery to this webhook with it, and your receiver uses it to tell real deliveries from forgeries in the next step.

    <Warning>
      Your account has **one** API webhook subscription — creating a webhook **replaces** the previous API webhook, but a legacy payment-alert webhook configured from the dashboard settings page is separate and stays put. Only a webhook registered via `POST /webhook` receives the events documented here.
    </Warning>
  </Step>

  <Step title="Confirm the subscription">
    [`GET /webhook`](/api-reference/webhook/get-webhooks) shows what's registered — after the call above, exactly one entry (you can also re-read the `secret` here if you lose it):

    ```json theme={null}
    {
      "webhooks": [
        {
          "id": "cmra19w22001msf02nuk4wo9m",
          "url": "https://example.com/webhooks/nickel/f3f9a2c81b7d",
          "secret": "whsec_your_webhook_signing_secret"
        }
      ]
    }
    ```

    <Note>
      `"secret": null` means the subscription predates webhook signing and its deliveries are unsigned — recreate it with `POST /webhook` to get a secret.
    </Note>
  </Step>

  <Step title="Handle deliveries">
    Every event arrives as a `POST` with the same thin envelope. Here's a real delivery:

    ```json theme={null}
    {
      "id": "097678b2-3161-4622-b226-8a7eac83b7fa",
      "created_at": "2026-07-07T02:34:36.498Z",
      "associated_object_type": "payment",
      "associated_object_id": "cmra1cu1h0020sf02v55com2p",
      "category": "payment.made",
      "type": "event"
    }
    ```

    Your receiver only needs to do five things — verify the signature, acknowledge fast, process later, deduplicate, and tolerate categories it doesn't know:

    ```javascript theme={null}
    app.post('/webhooks/nickel/f3f9a2c81b7d', (req, res) => {
      // verify the Nickel-Signature header against the raw body with your whsec_ secret
      if (!verifyNickelSignature(req.headers['nickel-signature'], req.rawBody, WEBHOOK_SECRET)) {
        return res.sendStatus(400) // not from Nickel
      }
      res.sendStatus(200) // acknowledge immediately — retries kick in after non-2xx

      const event = req.body
      if (alreadyProcessed(event.id)) return // deliveries can repeat; dedupe by event id

      switch (event.category) {
        case 'payment.made':
        case 'payment.failed':
          queueJob('syncPayment', event.associated_object_id)
          break
        // handle the categories you care about...
        default:
          break // ignore unknown categories — new ones may be added
      }
    })
    ```

    The `verifyNickelSignature` implementation — an HMAC-SHA256 over the timestamp and **raw** request body (capture it before your framework parses the JSON) — is in [Verifying Signatures](/concepts/webhooks#verifying-signatures).

    Nickel retries failed deliveries with exponential backoff for up to 5 attempts, so slow handlers cause duplicate deliveries — do real work on a queue, not in the request.
  </Step>

  <Step title="Fetch the real state">
    The event doesn't carry amounts or statuses — it tells you *what to look at*. Map `associated_object_type` to the matching `GET` endpoint:

    | `associated_object_type` | Fetch with                                                                                                         |
    | ------------------------ | ------------------------------------------------------------------------------------------------------------------ |
    | `payment`                | [`GET /payment/{id}`](/api-reference/payment/returns-a-payment-by-id)                                              |
    | `disbursement`           | [`GET /disbursement/{id}`](/api-reference/disbursement/returns-a-disbursement-by-id)                               |
    | `charge_authorization`   | [`GET /chargeAuthorization/{id}`](/api-reference/charge-authorization/returns-a-charge-authorization-by-id)        |
    | `bill_payment`           | [`GET /billPayment/{id}`](/api-reference/bill/get-a-bill-payment-by-id)                                            |
    | `vendor`                 | [`GET /vendor/{id}`](/api-reference/vendor/get-a-vendor-by-id)                                                     |
    | `check`                  | [`GET /billPayment/{id}`](/api-reference/bill/get-a-bill-payment-by-id) — check events carry the bill payment's ID |

    Always act on the fetched object, not the event name alone. For example, `payment.made` for a bank (ACH) payment fires when the debit is *initiated* — the fetched payment will show `status: "PENDING"`, not `SUCCEEDED`, until the funds actually arrive.
  </Step>

  <Step title="Test the loop in the sandbox">
    1. Point your sandbox subscription at a [webhook.site](https://webhook.site) inbox (or your ngrok tunnel).
    2. Trigger events with your sandbox key: create and pay an [invoice](/guides/send-an-invoice), or create a [charge authorization](/guides/autopay) — each fires its event within seconds.
    3. Watch the deliveries arrive, then swap in your real endpoint and re-register.
  </Step>
</Steps>

## Where to go next

<Columns cols={2}>
  <Card title="Webhook events" href="/concepts/webhooks" icon="bolt">
    The full event catalog, delivery retries, and signature verification.
  </Card>

  <Card title="Send an invoice and get paid" href="/guides/send-an-invoice" icon="envelope">
    The flow that fires payment.made.
  </Card>
</Columns>
