# Guide to Lithic Webhooks: Features and Best Practices

Lithic webhooks notify your systems about card and transaction activity: a card is created, a transaction updates, a dispute changes. If you're building card issuing or payments on Lithic, webhooks are how you react to account and transaction events without polling.

This guide covers how Lithic webhooks work, the events you'll handle, how to verify signatures (Lithic is Svix-powered and implements Standard Webhooks), secret rotation, and the best practices for production.

## What are Lithic webhooks?

Lithic webhooks are HTTP POSTs delivered to endpoints you configure, powered by Svix and implementing the [Standard Webhooks](https://www.standardwebhooks.com/) spec. Each request carries `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers (Svix also emits the `svix-id` / `svix-timestamp` / `svix-signature` aliases; Lithic's docs use the `webhook-*` names).

Because it's Standard Webhooks, verification is the familiar HMAC-over-`id.timestamp.body` scheme, and the official Lithic SDKs wrap it for you.

## Lithic webhook features

| Feature | Details |
| --- | --- |
| Configuration | Per-event-type subscriptions in the Lithic dashboard |
| Signature headers | `webhook-id`, `webhook-timestamp`, `webhook-signature` (Svix `svix-*` aliases too) |
| Signature scheme | base64 HMAC-SHA256 over `{webhook-id}.{webhook-timestamp}.{rawBody}` |
| Signing secret | Per-subscription, `whsec_`-prefixed (base64-decode the part after the prefix) |
| Replay protection | Reject timestamps outside ~5 minutes |
| Auto-disable | After ~5 continuous days of failed deliveries |
| SDKs | `lithic` (`webhooks.unwrap`), plus `svix` / `standardwebhooks` |

## Common events

Lithic event names use `resource.action`:

| Event | Fires when |
| --- | --- |
| `card.created` | A card is created |
| `card_transaction.updated` | A card transaction updates |
| `payment_transaction.created` | A payment transaction is created |
| `dispute.updated` | A dispute changes |

Subscribe to the event types you process (the transaction families are `card_transaction.*` and `payment_transaction.*`), and consult Lithic's events reference for the full list.

## Setting up Lithic webhooks

Configure per-event-type subscriptions and rotate secrets in the Lithic dashboard. Each subscription has its own `whsec_`-prefixed signing secret. Failed deliveries retry with backoff and the subscription auto-disables after about 5 continuous days of failures.

## Securing Lithic webhooks

Each delivery is signed with HMAC-SHA256 over `{webhook-id}.{webhook-timestamp}.{rawBody}`, using the base64-decoded portion of your `whsec_` secret as the key, and `webhook-signature` may carry multiple space-delimited `v1,<sig>` entries during rotation. The official Lithic SDK verifies and parses in one call:

```javascript
const Lithic = require("lithic");

const client = new Lithic();
const SECRET = process.env.LITHIC_WEBHOOK_SECRET; // whsec_...

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  try {
    // Verifies the signature + timestamp and returns the parsed event
    const event = client.webhooks.unwrap(req.body, req.headers, SECRET);
    res.sendStatus(200); // acknowledge fast
    processQueue.add(event); // process asynchronously
  } catch {
    res.sendStatus(400); // verification failed
  }
});

```

In Python, Lithic leans on the optional `standardwebhooks` package (the `svix` library also works directly):

```python
from standardwebhooks import Webhook

wh = Webhook(os.environ["LITHIC_WEBHOOK_SECRET"])  # whsec_...

@app.post("/webhook")
def webhook():
    try:
        event = wh.verify(request.get_data(), dict(request.headers))
    except Exception:
        return "", 400
    # process event asynchronously
    return "", 200

```

Verify against the raw request body, and reject timestamps outside the ~5-minute tolerance (the libraries handle both).

## Lithic webhook limitations and pain points

### Standard Webhooks details still bite

The Problem: The `whsec_` secret must be base64-decoded (after stripping the prefix) before it's the HMAC key, the signature is over `id.timestamp.body` (not the body alone), and rotation produces multiple `v1,<sig>` entries. Miss any and verification fails.

Why It Happens: These are Standard Webhooks/Svix mechanics.

Workarounds:

* Use the Lithic SDK (`webhooks.unwrap`) or the `svix` / `standardwebhooks` libraries, which handle all of it.

How Hookdeck Can Help: Hookdeck verifies Standard Webhooks signatures at the edge, including rotation, so your app receives pre-verified events. See our [guide to Svix webhooks](/webhooks/platforms/guide-to-svix-webhooks-features-and-best-practices).

### Auto-disable after sustained failures

The Problem: After ~5 continuous days of failed deliveries, the subscription auto-disables and you stop receiving events.

Why It Happens: Lithic (via Svix) prunes persistently failing endpoints.

Workarounds:

* Acknowledge fast so transient issues don't accumulate; monitor subscription status and re-enable.

How Hookdeck Can Help: Hookdeck always accepts deliveries and absorbs downstream failures itself, keeping the subscription active while it retries to your service.

### The 5-minute replay window

The Problem: Deliveries older than ~5 minutes are rejected. A backlogged handler can see events age out before verification.

Why It Happens: The freshness window is the scheme's replay protection.

Workarounds:

* Acknowledge fast and process asynchronously so verification happens within the window; keep clocks in sync.

How Hookdeck Can Help: Hookdeck verifies once at the edge and durably queues events, so a downstream backlog doesn't risk deliveries aging out.

### Duplicates

The Problem: Retries mean the same event can arrive more than once, and double-processing card or transaction events is costly.

Why It Happens: At-least-once delivery favors eventual delivery.

Workarounds:

* Dedupe on `webhook-id` and make side effects idempotent.

How Hookdeck Can Help: Hookdeck deduplicates on the message ID at the edge, so retries don't double-process. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

## Best practices

### Use the SDK or a Standard Webhooks library

`lithic`'s `webhooks.unwrap`, or `svix` / `standardwebhooks`, handle the base64 secret, the `id.timestamp.body` construction, rotation, and the timestamp window.

### Verify against the raw body

Capture the raw body before parsing so the HMAC matches.

### Acknowledge fast, process asynchronously

Return 2xx as soon as the signature verifies and defer work to a queue. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

### Dedupe on `webhook-id`

Use `webhook-id` as an idempotency key so retries don't double-process.

## Conclusion

Lithic webhooks are Svix-powered and implement Standard Webhooks: verify the `webhook-signature` over `id.timestamp.body` with the base64-decoded `whsec_` secret, reject stale timestamps, and dedupe on `webhook-id`. The official SDK's `webhooks.unwrap` handles verification in one call, and subscriptions auto-disable after ~5 days of failures.

[Hookdeck](https://hookdeck.com) verifies the signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique card and transaction events.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Lithic webhooks reliably in minutes.