Webhooks


Introduction

Domano can notify your application of events using webhooks. Each hook is cryptographically signed, so the request cannot be tampered with. If you receive a webhook, you can validate it to make sure it comes from us.

We also support various well-known chat apps, such as Discord and Slack.

Getting started

In order to make use of webhooks, you need to enable them by providing URLs for your webhook providers. You can find the webhook settings page in your top-right user dropdown menu.

Third party providers

We support Slack and Discord. You can find out how to set up webhooks on their sites. Once the webhook URL has been added to Domano, we'll automatically send a message through these webhooks, so you can get informed straight away if your domain(s) goes down:

Slack

Slack's official documentation explains in detail how to create your webhook.

Discord

Discord's official documentation explains in detail how to create your webhook.

Custom webhook

While we support Slack and Discord, you can also set up your own custom webhook. A custom webhook is fired the same way as the third party providers, however custom webhooks contains a lot more data. You get the raw payload and can act on it as you see fit.

How the custom webhooks works

Every event we fire internally, will also be translated to the webhook URL you provide.

This means you can receive the raw payload of uptime and downtime events. You can then use that information to update internal systems, escalate alerts, log events, send out emails to clients etc.

Our webhooks works by firing a POST request to the endpoint you specified. All data related to the event that just took place will be inside the POST payload.

Authentication

All webhooks we send will be signed by a signing secret, unique to your user. You can find the signing secret on your settings page.

You don't have to validate the incoming request, but it's highly recommended.

Our signing method is simple but efficient. For every webhook we call, we pass an additional header called Domano-Signature that contains the hash of the payload.

In your webhook, you can validate if that Domano-Signature header contains the hash you expected.

It's calculated like this:

$computedSignature = hash_hmac('sha256', $payload, $secret);

The $payload is the body of the POST request, which will be a JSON representation of the event.

The $secret is the one you can find on your settings page

The hash_hmac() function is a PHP function that generates a keyed hash value using the HMAC method.

The $computedSignature should match the Domano-Signature that's been set.

Validating webhooks - examples

private function isValidWebhook(Request $request, string $signature, string $payload): bool
{
    $signature = $request->header('Domano-Signature');
    $secret = env('WEBHOOK_SECRET');
    $computedSignature = hash_hmac('sha256', $payload, $secret);
    return hash_equals($signature, $computedSignature);
}
const crypto = require('crypto');

const verifyHMAC = (signature, payload, secret) => {
  // Make sure urls are properly escaped, https://domain.com will be transformed into https:\/\/domain.com e.g.
  // depending on your use case you might be able to skip the 'replace' step and take the data directly from the stream of a request object
  // but due to how Javascript parses json internally make sure the url is escaped like above
  const payloadEscaped = payload.replace(/\//g, '\\/');

  const computedSignature = crypto.createHmac('sha256', secret)
    .update(payloadEscaped, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(computedSignature),
    Buffer.from(signature)
  );
};
  
package main

import (
    "crypto/hmac"
    "crypto/sha256"
)

func verifyHMAC(payload, secret []byte, signature string) bool {
    mac := hmac.New(sha256.New, secret)
    mac.Write(payload)
    expectedHMAC := mac.Sum(nil)

    signatureMAC, err := hex.DecodeString(signature)
    if err != nil {
      return false
    }

    return hmac.Equal(signatureMAC, expectedHMAC)
}
import hmac, hashlib

def verifySignature(payload, signature, secret):
    expectedHMAC = hmac.new(
        key = secret.encode('utf-8'),
        msg = payload.encode('utf-8'),
        digestmod = hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expectedHMAC)

Events and payload

Below you'll see the list of events we're currently supporting through custom webhooks.

Type id Type Description
1 uptime_check_success When a domain sends back 200 after previously marked as failed
2 uptime_check_fail When a domain sends back anything else than 200

uptime_check_success payload

{
  "type_id": 1,
  "type": "uptime_check_success",
  "date": "2019-05-17 00:22:22",
  "date_unix": 1558045342,
  "status_code": 200,
  "domain": {
    "id": 1,
    "url": "https:\/\/kaem.dk",
    "host": "kaem.dk",
    "title": "KAEM (DK)",
    "uses_https": true
  }
}

uptime_check_fail payload

{
  "type_id": 2,
  "type": "uptime_check_fail",
  "date": "2019-05-17 00:16:48",
  "date_unix": 1558045008,
  "status_code": 500,
  "domain": {
    "id": 23,
    "url": "https:\/\/kaem.io",
    "host": "kaem.io",
    "title": "KAEM (IO)",
    "uses_https": true
  },
  "client": {
    "name": "JUMDUM",
    "email": "mail@jumdum.com",
    "phone": null,
    "address": null,
    "attention": "Thomas",
    "cvr": null,
    "vat": null
  }
}

If your domain is registered under a client of yours, the client information will automatically come with the webhook payload, as seen above.