Webhooks

Webhooks allow you to receive real-time notifications when events occur in your Badge workspace. Instead of polling our API for updates, Badge will send HTTP POST requests to your specified endpoint(s) whenever subscribed events occur.

Available Event Topics

Event TopicDescription
pass.activatedTriggered when a user adds the pass to their wallet OR re-enables push notifications/automatic updates for an existing pass.
pass.deactivatedTriggered when a user removes the pass from their wallet OR disables push notifications/automatic updates.

How to Implement Webhooks

1. Create a Webhook Endpoint

First, create an endpoint in your application that will receive webhook events. This endpoint must:

  • Accept HTTP POST requests
  • Respond with a 2xx status code (200-299) within 15 seconds
  • Be publicly accessible via HTTPS

2. Register Your Endpoint

Register your webhook endpoint using the Badge API:

POST https://api.badge.com/v0/webhookEndpoints HTTP/1.1
Accept: application/json
Authorization: Bearer API_KEY
Content-Type: application/json

{
  "enabledTopics": [
    "pass.activated",
    "pass.deactivated"
  ],
  "enabled": true,
  "url": "https://example.com/badge-webhook"
}
{
  "id": "ep_36zldNI3VprQODUezjRsmHxS8ky",
  "secret": "whsec_5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH",
  "url": "https://your-api.com/badge-webhook-processor",
  "enabledTopics": ["pass.activated", "pass.deactivated"],
  "enabled": true,
  "createdAt": "2025-01-01T00:00:00.000Z"
}

Important: Save the secret value from the response securely. You'll need it to verify webhook event signatures. This secret is only returned when creating the endpoint and cannot be retrieved later.

3. Verify Webhook Signatures

All webhook requests from Badge include cryptographic signatures to verify their authenticity. We highly recommend verifying the signatures to ensure the webhook was sent by Badge.

Signature Headers

Each webhook request includes three headers:

  • webhook-id: the unique message identifier for the webhook message. This identifier is unique across all messages, but will be the same when the same webhook is being resent (e.g. due to a previous failure).
  • webhook-timestamp: timestamp in seconds since the Unix epoch.
  • webhook-signature: list of space delimited signatures and their corresponding version identifiers.

Verification Process

To verify a webhook signature:

  1. Extract the headers and the raw body from the request.
  2. Construct the signed content: ${webhook_id}.${webhook_timestamp}.${raw_request_body}
  3. Compute the expected signature using HMAC-SHA256 with your webhook secret.
  4. Compare the expected signature matches one of the provided signatures.

Example

import crypto from "node:crypto";

const yourSecret = "whsec_5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH";

const webhookId = request.get("Webhook-Id");
const webhookSignature = request.get("Webhook-Signature");
const webhookTimestamp = request.get("Webhook-Timestamp");
const body = request.body;

const signedContent = `${webhookId}.${webhookTimestamp}.${body}`;

const secretBytes = Buffer.from(yourSecret.split("_")[1], "base64");
const digest = crypto
  .createHmac("sha256", secretBytes)
  .update(signedContent)
  .digest("base64");
const expectedSignature = `v1,${digest}`;

const isValid = webhookSignature.split(" ").includes(expectedSignature);

Tips

Use the Correct Signature

The webhook-signature header is composed of a list of space delimited signatures and their corresponding version identifiers. For example: v1,signature1 v2,signature2. Make sure to remove the version prefix and delimiter (e.g. v1,) before verifying the signature.

Use the Raw Request Body

You must verify the signature against the raw, unparsed request body. If you parse the JSON first, the signature verification will fail due to whitespace and formatting differences.

Validate the Timestamp

Check that the webhook-timestamp is recent to prevent replay attacks where an attacker captures a valid webhook and sends it again later.

Retries

We attempt to deliver each webhook message based on a retry schedule with exponential backoff. Each message is attempted based on the following schedule, where each period is started following the failure of the preceding attempt:

  • Immediately
  • 5 seconds
  • 5 minutes
  • 30 minutes
  • 2 hours
  • 5 hours
  • 10 hours
  • 10 hours (in addition to the previous)

If an endpoint is removed or disabled delivery attempts to the endpoint will be disabled as well.

For example, an attempt that fails three times before eventually succeeding will be delivered roughly 35 minutes and 5 seconds following the first attempt.

Best Practices

Handle Idempotency

Badge offers "at least once" delivery semantics. This means that if there are issues during delivery (e.g. network issues) a message can sometimes be received multiple times by the webhook receiver.

Badge includes a unique event id with every webhook request. That ID is unique per event but is reused across message retries. When "exactly once" semantics are required, consumers can then use this ID to ensure that they only process each event once.

Respond Quickly

Your webhook endpoint should respond with a 200 status code as quickly as possible. If you need to perform time-consuming operations, queue the event for asynchronous processing. We will consider any message that fails to send a response within 15 seconds a failed message.

IP Whitelisting

Webhook events from Badge will use the following IP addresses. You can add these IP addresses to your application/firewall allowlist.

44.228.126.217
50.112.21.217
52.24.126.164
54.148.139.208
2600:1f24:64:8000::/56