Webhooks are an important part of your SSL management. They allow BrandSSL to notify you about events that happen on your account, such as a domain name being added or disabled.

A webhook URL is an endpoint on your server where you can receive notifications about such events. When an event occurs, we'll make a POST request to that endpoint, with a JSON body containing the details about the event, including the type of event and the data associated with it.

Structure of a webhook payload

All webhook payloads follow the same basic structure:

  • an event field describing the type of event
  • a data object. The contents of this object will vary depending on the event, but typically it will contain details of the event, including:
  • an id containing the ID of the resource
  • a status, describing the status of the request
  • domain or endpoint details, if applicable

Here are some sample webhook payloads for domain management:
{
  "event": "domain.created",
  "data": {
    "id": "907589706",
    "url": "www.turningpoint.com",
    "endpoint": {},
    "timestamp": "2022-06-16 14:58:47.326591+00:00",
    "admin_graphql_api_id": "gid://brandssl/Domain/907589706"
  }
}
{
  "event": "domain.disabled",
  "data": {
    "id": "907589706",
    "url": "www.turningpoint.com",
    "endpoint": {},
    "timestamp": "2022-06-16 14:58:47.326591+00:00",
    "admin_graphql_api_id": "gid://brandssl/Domain/907589706"
  }
}
{
  "event": "domain.created",
  "data": {
    "id": "907589706",
    "url": "www.turningpoint.com",
    "endpoint": {
      "id": "75789189brandssl-staging",
      "name": "BrandSSL Staging"
    },
    "timestamp": "2022-06-16 14:58:47.326591+00:00",
    "admin_graphql_api_id": "gid://brandssl/Domain/907589706"
  }
}
{
  "event": "domain.deleted",
  "data": {
    "id": "907589706",
    "url": "www.turningpoint.com",
    "endpoint": {},
    "timestamp": "2022-06-16 14:58:47.326591+00:00",
    "admin_graphql_api_id": "gid://brandssl/Domain/907589706"
  }
}

Create a webhook URL

A webhook URL is simply a POST endpoint that a resource server sends updates to. The URL needs to parse a JSON request and return a 200 OK:

// Using Express
app.post("/my/webhook/url", function(req, res) {
    // Retrieve the request's body
    const event = req.body;
    // Do something with event
    res.send(200);
});
<?php
    // Retrieve the request's body and parse it as JSON
    $input = @file_get_contents("php://input");
    $event = json_decode($input);
    // Do something with $event
    http_response_code(200); // PHP 5.4 or greater
?>

When your webhook URL receives an event, it needs to parse and acknowledge the event. Acknowledging an event means returning a 200 OK in the HTTP header. Without a 200 OK in the response header, we’ll keep sending events for the next 36 hours:

Avoid long-running tasks
If you have long-running tasks in your webhook function, you should acknowledge the event before executing the long-running tasks. Long-running tasks will lead to a request timeout and an automatic error response from your server. Without a 200 OK response, we retry as described in the paragraph above.

Enabling webhooks

Here's how to set up a webhook on your BrandSSL account:

  • Log in to your dashboard and click on Settings
  • Navigate to Webhooks to add your webhook URL
  • Save your settings
Tip
When testing, you can get an instant webhook URL by visiting webhook.site. This will allow you to inspect the received payload without having to write any code or set up a server.

Verifying webhook signatures

When enabling webhooks, you have the option to set a secret hash. Since webhook URLs are publicly accessible, the secret hash allows you to verify that incoming requests are from BrandSSL. You can specify any value as your secret hash, but we recommend something random. You should also store it as an environment variable on your server.

If you specify a secret hash, we'll include it in our request to your webhook URL, in a header called x-brandssl-signature. In the webhook endpoint, check if the x-brandssl-signature header is present and that it matches the secret hash you set. If the header is missing, or the value doesn't match, you can discard the request, as it isn't from BrandSSL.

You may need to disable CSRF protection
Some web frameworks like Rails or Django, automatically check that every POST request contains a CSRF token. This is a useful security feature that protects you and your users from cross-site request forgery.

However, for webhooks to work, you'll need to exempt the webhooks endpoint from CSRF protection (demonstrated in the examples below).
// In an Express-like app:

app.post("/brandssl-webhook", (req, res) => {
    // If you specified a secret hash, check for the signature
    const secretHash = process.env.BRANDSSL_SECRET_HASH;
    const signature = req.headers["x-brandssl-signature"];
    if (!signature || (signature !== secretHash)) {
        // This request isn't from BrandSSL; discard
        res.status(401).end();
    }
    const payload = req.body;
    // It's a good idea to log all received events.
    log(payload);
    // Do something (that doesn't take too long) with the payload
    res.status(200).end()
});
// In a Laravel-like app:

Route::post('/brandssl-webhook', function (\Illuminate\Http\Request $request) {
    // If you specified a secret hash, check for the signature
    $secretHash = config('services.brandssl.secret_hash');
    $signature = $request->header('x-brandssl-signature');
    if (!$signature || ($signature !== $secretHash)) {
        // This request isn't from BrandSSL; discard
        abort(401);
    }
    $payload = $request->all();
    // It's a good idea to log all received events.
    Log::info($payload);
    // Do something (that doesn't take too long) with the payload
    return response(200);
});
# In a Django-like app:
import os

@require_POST
@csrf_exempt
def webhook(request):
    secret_hash = os.getenv("BRANDSSL_SECRET_HASH")
    signature = request.headers.get("x-brandssl-signature")
    if signature == None or (signature != secret_hash):
        # This request isn't from BrandSSL; discard
        return HttpResponse(status=401)
    payload = request.body
    # It's a good idea to log all received events.
    log(payload)
    # Do something (that doesn't take too long) with the payload
    return HttpResponse(status=200)
# In a Rails-like app:

class WebhookController < ApplicationController
    protect_from_forgery except: :webhook

    def webhook
        secret_hash = ENV["BRANDSSL_SECRET_HASH"]
        signature = request.headers["HTTP_X_BRANDSSL_SIGNATURE"]
        if !signature || (signature != secret_hash)
            # This request isn't from BrandSSL; discard
            head :unauthorized
            return
        end
        payload = params
        # It's a good idea to log all received events.
        Log.info payload
        # Do something (that doesn't take too long) with the payload
        head :ok
    end
end

Types of events

Here are the events we currently raise. We would add more to this list as we hook into more actions in the future.

EventDescription
domain.createdA new domain name has been created
domain.disabledA domain name has been disabled
domain.deletedA domain name has been deleted

Best practices

Use a secret hash

Remember, your webhook URL is public, and anyone can send a fake payload. We recommend using a secret hash so you can be sure the requests you get are from BrandSSL.

Respond quickly

Your webhook endpoint needs to respond within a certain time limit, or we'll consider it a failure and try again. Avoid doing long-running tasks or network calls in your webhook endpoint so you don't hit the timeout.

If your framework supports it, you can have your webhook endpoint immediately return a 200 status code, and then perform the rest of its duties; otherwise, you should dispatch any long-running tasks to a job queue, and then respond.

Be idempotent

Occasionally, we might send the same webhook event more than once. You should make your event processing idempotent (calling the webhook multiple times will have the same effect)

One way of doing this is recording the events you've processed, and then checking if the status has changed before processing the duplicate event: