Webhooks

Webhooks let you receive real-time notifications when events happen in Breakwater — licenses created, customers updated, tokens revoked, and more. Instead of polling the API, Breakwater sends an HTTP POST to your endpoint whenever a subscribed event occurs.

What is a Webhook Endpoint?

A webhook endpoint is an HTTPS URL on your server that Breakwater sends event data to. Each endpoint has:

  • URL: Where to send the events
  • Event subscriptions: Which event types to receive
  • Signing secret: A shared secret used to verify that requests came from Breakwater
  • Enabled/disabled status: Whether the endpoint is currently receiving events

You can register multiple endpoints to route different events to different systems — for example, license events to your CRM and auth token events to your security dashboard.

Creating an Endpoint

  1. Navigate to Webhooks in the vendor portal
  2. Click New Webhook Endpoint
  3. Fill in the endpoint details:
    • URL (required): The HTTPS URL that will receive events
    • Name: A descriptive name for the endpoint
    • Description: Optional notes about its purpose
    • Event types: Check the events you want this endpoint to receive
  4. Click Create Webhook Endpoint
  5. Copy the signing secret immediately — it won't be shown again

The signing secret is displayed once after creation. Store it securely in your receiving application's configuration.

Event Types

Events are organized by resource. When creating or editing an endpoint, select which events it should receive:

Licenses

Event Fired when
license.created A new license is created
license.activated A license transitions to active status
license.expired A license passes its expiration date
license.cancelled A license is cancelled

Customers

Event Fired when
customer.created A new customer is created
customer.updated A customer's details are updated
customer.deleted A customer is deleted

Auth Tokens

Event Fired when
auth_token.created A new auth token is issued
auth_token.revoked An auth token is revoked

Products

Event Fired when
product.created A new product is created
product.updated A product is updated
product.deleted A product is deleted

Repositories

Event Fired when
repository.created A new repository is created
repository.deleted A repository is deleted

Payload Format

Every webhook delivery is an HTTP POST with a JSON body using this envelope structure:

{
  "id": "whev_abc123",
  "type": "license.created",
  "created_at": "2026-02-24T12:00:00Z",
  "data": {
    "id": 42,
    "product_id": "prod_abc123",
    "customer_id": "cust_def456",
    "status": "pending",
    "starts_at": "2026-03-01T00:00:00Z",
    "expires_at": "2027-03-01T00:00:00Z"
  },
  "links": {
    "api_url": "https://app.breakwater.dev/api/v1/vendor/customers/cust_def456/licenses/lic_ghi789"
  }
}
Field Description
id Unique event identifier
type The event type string
created_at When the event occurred (ISO 8601)
data The full serialized resource at the time of the event
links.api_url API URL to fetch the current state of the resource

The data field contains the same structure as the corresponding API response, so you can process events without making follow-up API calls. If you need the latest state (e.g., it may have changed since the event), use the links.api_url to fetch it.

Verifying Signatures

Every webhook request includes two headers:

Header Description
X-Breakwater-Signature Hex-encoded HMAC-SHA256 signature
X-Breakwater-Timestamp Unix timestamp of when the request was signed

The signed content is the timestamp and the raw request body joined by a period: {timestamp}.{body}.

To verify a webhook:

  1. Extract the X-Breakwater-Signature and X-Breakwater-Timestamp headers
  2. Construct the signed content: "{timestamp}.{raw_body}"
  3. Compute the HMAC-SHA256 of the signed content using your signing secret
  4. Compare your computed signature with the one in the header using a constant-time comparison

Ruby

def verify_webhook(request, signing_secret)
  signature = request.headers["X-Breakwater-Signature"]
  timestamp = request.headers["X-Breakwater-Timestamp"]
  body = request.body.read

  expected = OpenSSL::HMAC.hexdigest("SHA256", signing_secret, "#{timestamp}.#{body}")
  ActiveSupport::SecurityUtils.secure_compare(signature, expected)
end

Node.js

const crypto = require("crypto");

function verifyWebhook(body, signature, timestamp, signingSecret) {
  const expected = crypto
    .createHmac("sha256", signingSecret)
    .update(`${timestamp}.${body}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Python

import hashlib, hmac

def verify_webhook(body, signature, timestamp, signing_secret):
    expected = hmac.new(
        signing_secret.encode(),
        f"{timestamp}.{body}".encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

Replay Protection

To guard against replay attacks, reject requests where the timestamp is more than a few minutes old:

timestamp = request.headers["X-Breakwater-Timestamp"].to_i
if Time.now.to_i - timestamp > 300 # 5 minutes
  head :unauthorized
  return
end

Testing Your Endpoint

Before relying on webhooks in production, verify your integration works:

  1. Navigate to the endpoint's detail page
  2. Click Send Test
  3. Breakwater sends a test.ping event to your URL
  4. Check the delivery log on the same page to confirm it succeeded

The test ping endpoint must be enabled to send a test. If it's disabled, re-enable it first.

Delivery Log

Each endpoint has a delivery log showing recent deliveries with:

  • Event type: What triggered the delivery
  • Status: Whether it succeeded, failed, or is being retried
  • HTTP response code: The status code your server returned
  • Timestamp: When the delivery was attempted

Use the delivery log to debug integration issues. Delivery logs are retained for 30 days.

Retries

If your endpoint returns a non-2xx response or is unreachable, Breakwater retries the delivery with exponential backoff:

Attempt Retry after
1 30 seconds
2 2 minutes
3 15 minutes
4 1 hour
5 4 hours
6 24 hours

After 6 failed attempts, the delivery is marked as exhausted.

Auto-Disable

If 3 separate events each exhaust all retry attempts, the endpoint is automatically disabled and you'll receive an email notification. This prevents Breakwater from continuing to send to a broken endpoint.

To recover:

  1. Fix the issue with your receiving server
  2. Navigate to the endpoint in the vendor portal
  3. Click Send Test to verify it's working (re-enable first if needed)
  4. Re-enable the endpoint

The failure counter resets when you re-enable an endpoint.

Managing Endpoints

Editing

  1. Click on the endpoint to view details
  2. Click Edit
  3. Update the URL, name, description, enabled status, or event subscriptions
  4. Click Update Webhook Endpoint

Disabling

You can disable an endpoint without deleting it — useful during maintenance:

  1. Edit the endpoint
  2. Uncheck Enabled
  3. Save

While disabled, no events are delivered. Events that occur while the endpoint is disabled are not queued or delivered later.

Deleting

  1. Click on the endpoint to view details
  2. Click Delete
  3. Confirm the deletion

Deleting an endpoint removes it and all its delivery history.

Regenerating the Signing Secret

If your signing secret is compromised:

  1. Delete the endpoint
  2. Create a new endpoint with the same URL and event subscriptions
  3. Update your receiving application with the new signing secret

Best Practices

Security

  • Always verify signatures — Don't trust webhook payloads without checking the HMAC signature
  • Use HTTPS — Always use HTTPS URLs to protect webhook payloads in transit
  • Check timestamps — Reject requests with old timestamps to prevent replay attacks
  • Store secrets securely — Keep your signing secret in environment variables, not in source code

Reliability

  • Respond quickly — Return a 2xx response within 10 seconds. Process the event asynchronously if it takes longer
  • Handle duplicates — In rare cases, the same event may be delivered more than once. Use the event id to deduplicate
  • Monitor the delivery log — Check periodically to catch issues before the endpoint gets auto-disabled

Architecture

  • One endpoint per system — Route different events to different endpoints rather than building a single dispatcher
  • Subscribe only to what you need — Fewer subscriptions means less noise and less processing
  • Use the type field — Always check the event type before processing, even if you only subscribe to one type
Last updated March 12, 2026