Skip to main content

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.

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 specification. You don’t need to write the check yourself.

Verify with an official library

Use the official Standard Webhooks 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.
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.
Node.js
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 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:
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 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.
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.

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.