# Guide to Faundit Webhooks: Features and Best Practices

Faundit webhooks notify your application when the status of a lost-and-found item or a return request changes. If you're building on Faundit, webhooks are how you react to status changes without polling.

This guide covers how Faundit webhooks work, the two events you'll handle, how to verify the `X-Faundit-Signature-Next`, and why to avoid the deprecated v0 header.

## What are Faundit webhooks?

Faundit webhooks are HTTP POSTs delivered to a URL you configure. The current (v1) scheme signs each delivery with an `X-Faundit-Signature-Next` header: an HMAC-SHA256 (hex) over `v1:<timestamp>:<body>`, where `<timestamp>` is the `X-Faundit-Timestamp` header value and `<body>` is the raw, unparsed request body. A deprecated v0 scheme sends `X-Faundit-Signature` over `v0:<timestamp>` only, which doesn't cover the body, so prefer the `-Next` header. This is not Standard Webhooks.

## Faundit webhook features

| Feature | Details |
| --- | --- |
| Configuration | Register your endpoint; request the signing secret from tech@faundit.com |
| Signature header | `X-Faundit-Signature-Next` (current), `X-Faundit-Signature` (deprecated v0) |
| Signature scheme | HMAC-SHA256 (hex) over `v1:<timestamp>:<body>`, keyed with the signing secret |
| Timestamp | Sent in `X-Faundit-Timestamp` |
| Events | `item-status`, `request-status` |
| SDK | None |

## Common events

Faundit has exactly two event types. The granular statuses (delivered, finished, expired) are fields in the payload, not separate events.

| Event | Fires when |
| --- | --- |
| `item-status` | An item's status changes |
| `request-status` | A request's status changes |

Branch on the event type, then read the status field for the specifics.

## Setting up Faundit webhooks

Register your endpoint with Faundit and request the signing secret from tech@faundit.com (it isn't self-service). If you're on API v2, note that Members / `faundit_memberID` were renamed to Locations / `locationID`, though legacy IDs are still accepted.

## Securing Faundit webhooks

The current header is `X-Faundit-Signature-Next`. To verify, build `v1:<timestamp>:<body>` using the `X-Faundit-Timestamp` value and the raw body, compute an HMAC-SHA256 with your signing secret, hex-encode it, and compare. Use the raw body before JSON parsing.

```javascript
const crypto = require("crypto");

const SECRET = process.env.FAUNDIT_WEBHOOK_SECRET;

function verify(rawBody, timestamp, signature) {
  const message = `v1:${timestamp}:${rawBody.toString()}`;
  const expected = crypto.createHmac("sha256", SECRET).update(message).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const ok = verify(
    req.body,
    req.headers["x-faundit-timestamp"],
    req.headers["x-faundit-signature-next"] // prefer -Next over the deprecated v0 header
  );
  if (!ok) return res.sendStatus(401);

  res.sendStatus(200); // acknowledge fast
  processQueue.add(JSON.parse(req.body)); // branch on event type, async
});

```

The same check in Python:

```python
import hashlib
import hmac
import os

SECRET = os.environ["FAUNDIT_WEBHOOK_SECRET"].encode()

def verify(raw_body: bytes, timestamp: str, signature: str) -> bool:
    message = (f"v1:{timestamp}:").encode() + raw_body
    expected = hmac.new(SECRET, message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

```

## Faundit webhook limitations and pain points

### The deprecated v0 header doesn't cover the body

The Problem: The old `X-Faundit-Signature` signs `v0:<timestamp>` only, with no body integrity. Verifying against it means an attacker can change the body without invalidating the signature.

Why It Happens: The v0 scheme predates body signing.

Workarounds:

* Use `X-Faundit-Signature-Next` (the `v1:<timestamp>:<body>` scheme) and ignore v0.

How Hookdeck Can Help: Hookdeck verifies the current scheme at the edge, so your app isn't exposed to the weaker v0 signature.

### The signed message is prefixed and colon-joined

The Problem: The HMAC is over `v1:<timestamp>:<body>`, not the body alone. Signing the body by itself, or forgetting the `v1:` prefix, fails.

Why It Happens: Faundit versions and timestamps the signed message.

Workarounds:

* Reconstruct the exact `v1:<timestamp>:<body>` string from the raw body and the `X-Faundit-Timestamp` header.

How Hookdeck Can Help: Hookdeck reconstructs and verifies the signed message at the edge.

### The secret isn't self-service

The Problem: You can't generate the signing secret yourself, you request it from Faundit, which slows setup.

Why It Happens: Faundit issues secrets manually.

Workarounds:

* Request the secret from tech@faundit.com early, and store it securely.

How Hookdeck Can Help: Hookdeck holds the secret and verifies centrally, so it lives in one place rather than across every service.

### Only two events, with status in the payload

The Problem: There are only `item-status` and `request-status`. Code that expects granular events like `delivered` or `expired` never fires, because those are payload fields.

Why It Happens: Faundit models granularity as status fields, not event names.

Workarounds:

* Branch on the event type, then read the status field.

How Hookdeck Can Help: Hookdeck's filters can route on the status field inside the payload, giving you status-level routing.

## Best practices

### Verify over `v1:<timestamp>:<body>` with the signing secret

Reconstruct the prefixed, colon-joined message from the raw body and `X-Faundit-Timestamp`, HMAC-SHA256, hex, and compare against `X-Faundit-Signature-Next` in constant time.

### Prefer the `-Next` header

Ignore the deprecated `X-Faundit-Signature` (v0) that omits the body.

### Read the status field

Branch on `item-status` / `request-status`, then handle the granular status from the payload.

### Return 200 and process asynchronously

Acknowledge quickly and defer work to a queue. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

Faundit webhooks are verified with the current `X-Faundit-Signature-Next` header, an HMAC-SHA256 over `v1:<timestamp>:<body>` keyed with your signing secret, using the timestamp from `X-Faundit-Timestamp`. Avoid the deprecated v0 header that signs only the timestamp. Verify over the raw body, branch on the two event types, and read the granular status from the payload.

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

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