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

# Best practices

> Review these best practices to make sure your webhooks remain secure and function well with your integration.

## Handle duplicate events

Webhook endpoints might occasionally receive the same message more than once. A message is one event sent to one webhook. For example, if your endpoint processes a message but answers after our 16 second timeout, the attempt counts as failed and we send the message again. Make your event processing idempotent: log the `webhook-id` of every message you’ve processed, and skip the ones you’ve already logged.

Use the `webhook-id` header alone as the idempotency key.

* `webhook-id` is unique to each message, and it stays the same on every retry of that message.
* If two of your webhooks subscribe to the same brand, each one receives its own message, with its own `webhook-id`.
* `webhook-delivery-id` and the body’s `delivery` are new on every attempt. Don’t deduplicate on them.

## Only listen to event types your integration requires

Configure your webhook endpoints to receive only the types of events required by your integration. Listening for extra events (or all events) puts undue strain on your server and we don’t recommend it.

You can change the events that a webhook endpoint receives by updating the webhook object using the `updateWebhook` [mutation](/delivery-methods/graphql).

## Handle events asynchrounously

The volume of events that get generated can be spike-y. A data enrichment may result in millions of objects being updated in a very short period of time. Configure your handler to process incoming events with an asynchronous queue. You might encounter scalability issues if you choose to process events synchronously. Any large spike in webhook deliveries (for example, during a large data enrichment) might overwhelm your endpoint hosts.

Asynchronous queues allow you to process the concurrent events at a rate your system can support. For example, your webhook endpoint could simply verify a payload and then push the event directly to a queue (like AWS SQS) where you can then process the events with better concurrency control.

## Receive events with an HTTPS server

You must use an HTTPS URL for your webhook endpoint. Brandfetch validates that the connection to your server is secure before sending your webhook data. For this to work, your server must be correctly configured to support HTTPS with a valid server certificate.

## Verify events are sent from Brandfetch

Brandfetch signs every delivery. Check the signature before you act on the body: it proves Brandfetch sent the request, and that nobody changed it on the way.

Signatures follow version 1 of the [Standard Webhooks](https://www.standardwebhooks.com) specification. You don’t need to write the check yourself.

### Verify with an official library

Use the [official Standard Webhooks libraries](https://github.com/standard-webhooks/standard-webhooks/tree/main/libraries). Each one takes your webhook’s secret, the raw request body, and the request headers, and checks the signature and the timestamp. If either check fails, the library throws an error: answer `400` and drop the delivery.

Your secret is the webhook’s `secret` field. `createWebhook` returns it, and the `webhook` and `webhooks` queries read it back. Pass it to the library exactly as shown, `whsec_` prefix included.

<Warning>
  Pass the raw request body, byte for byte. If your framework parses the JSON first, serializing it again changes the bytes, and the signature won’t match.
</Warning>

```javascript Node.js theme={null}
import express from "express";
import { Webhook } from "standardwebhooks";

const webhook = new Webhook(process.env.BRANDFETCH_WEBHOOK_SECRET);
const app = express();

// express.raw() keeps the body as the exact bytes Brandfetch signed.
// A delivery can be larger than its default 100 KB limit.
app.post(
  "/webhooks/brandfetch",
  express.raw({ type: "application/json", limit: "1mb" }),
  (req, res) => {
    let event;
    try {
      event = webhook.verify(req.body, req.headers);
    } catch {
      return res.status(400).send("Invalid signature");
    }

    // Queue the event for processing, then answer quickly.
    console.log(event.type, event.data.urn);
    res.sendStatus(204);
  },
);

app.listen(process.env.PORT ?? 3000);
```

Install both packages with `npm install standardwebhooks express`. The sample is an ES module: save it as a `.mjs` file, or set `"type": "module"` in your `package.json`.

### Verify without a library

If no official library fits your stack, implement the check exactly as the specification describes:

1. Read the `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers.
2. Check that `webhook-timestamp`, a Unix timestamp in seconds, is within 5 minutes of your clock.
3. Build the signed content: the `webhook-id`, the `webhook-timestamp`, and the raw body, joined with periods (`.`): `{webhook-id}.{webhook-timestamp}.{body}`.
4. Build the key: remove `whsec_` from the start of your secret, and base64-decode the rest.
5. Compute the HMAC-SHA256 of the signed content with that key, and base64-encode it.
6. Compare it with the signatures in `webhook-signature`. The header holds one or more signatures separated by spaces, each written `v1,<base64>`. Accept the delivery if any `v1` signature matches. Compare with a constant-time function, never with `==`.

## Preventing replay attacks

A replay attack is when an attacker intercepts a valid payload and its signature, then re-transmits them. The `webhook-timestamp` header stops this. It holds the time of the attempt as a Unix timestamp in seconds, and it is part of the signed content, so an attacker can’t change it without invalidating the signature. Reject a delivery whose timestamp is more than 5 minutes from your clock. The official libraries do this for you. A replay inside those 5 minutes carries a `webhook-id` you’ve already processed, so [deduplicating on it](#handle-duplicate-events) stops that too.

Use Network Time Protocol (NTP) to keep your server’s clock accurate. A clock that drifts past the tolerance rejects genuine deliveries.

Brandfetch generates the timestamp and signature each time we send an event to your endpoint. A retry gets a new timestamp and a new signature, so the retry of an event from days ago still passes the check.

## Source IPs and allow-listing

Brandfetch sends every webhook delivery from one of these IP addresses:

```text theme={null}
50.19.252.245
184.194.132.215
50.17.194.218
```

Allow all three. Any delivery can come from any of them, and a retry can come from a different address than the first attempt. We announce any change to this list in the [changelog](/changelog/overview) before it takes effect.

Every Brandfetch webhook shares these addresses. An allow-list proves a request came from Brandfetch, not that it came from your webhook. Keep two more checks in place:

1. **At the perimeter, check the `Authorization` header.** Brandfetch sends the value you set with `urlHeaders` with every delivery. Have your load balancer, API gateway, or WAF drop any request whose `Authorization` header is not exactly this value. A check that the header is present protects nothing.
2. **In your application, verify the signature.** The token keeps stray traffic away from your application. The signature proves Brandfetch sent the body, and that nobody changed it.

Set the header in the `createWebhook` or `updateWebhook` input. Use a long random token, such as the output of `openssl rand -hex 32`. The value must be printable ASCII, with no line breaks. The API never returns it once it’s set.

```json theme={null}
{ "urlHeaders": [{ "name": "Authorization", "value": "Bearer <your random token>" }] }
```

If a WAF or bot protection sits in front of your endpoint, exempt deliveries from its challenges, for example by skipping them for requests that carry your exact `Authorization` value. Brandfetch reads only the status code. A challenge page served with a non-2xx status fails the attempt, and the delivery is retried. One served with a 2xx status counts as delivered, and the event never reaches your application. An endpoint that fails for 14 days straight is [switched off](/delivery-methods/webhooks/delivery-behaviors#disable-behavior).

## Quickly return a 2xx response

Your endpoint must quickly return a successful status code (2xx) prior to any complex logic that could cause a timeout. For example, you must return a 200 response before updating any records in your database or making additional API requests. We wait 16 seconds for a response. A slower answer counts as a failed attempt, and the event is sent again.

Answer the delivery at the URL you registered. Brandfetch doesn’t follow redirects: a 3xx response is a failed attempt, and it is retried like any other non-2xx status.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.