Learn

Alert webhooks

Get every DomainToolBelt alert as a signed JSON POST to your own HTTPS address, for your scripts, Slack or Zapier flows.

Who can use them

Webhooks come with a paid site plan or topped-up API credits. You get one webhook address per account, and it receives every alert your watched domains, keywords and monitors raise, the same alerts you see in the dashboard.

Set it up

  1. Open Settings, then Notifications, and paste an https:// address you control.
  2. Copy the signing secret shown there and keep it on your server. You can make a new one at any time; the old one stops working at once.
  3. Press Send test. We send one signed test alert and show the answer your server gave.
  4. Switch the webhook on.

You can do the same from code with GET and PUT /v1/webhook and POST /v1/webhook/test, or ask your assistant to use the MCP tools get_webhook, set_webhook and test_webhook.

What we send

One POST per alert with a JSON body:

Example body
{
  "id": "b4f1c2d0-5e2a-4c1b-9a77-0c2f1e9d3a10",
  "type": "dropped",
  "domain": "brewly.com",
  "title": "brewly.com dropped",
  "detail": "The name was deleted and can be registered again.",
  "time": "2026-10-31T14:02:11.000Z",
  "link": "https://domaintoolbelt.com/domain/brewly.com"
}
Headers on every webhook POST
HeaderMeaning
X-DTB-Eventalert, or test for a test send.
X-DTB-DeliveryThe alert id. The same id on a retry, so you can skip duplicates.
X-DTB-TimestampUnix time in seconds when the POST was signed.
X-DTB-Signaturesha256= followed by the HMAC-SHA256 of "{timestamp}.{body}" with your signing secret, in hex.

Event types

The type field says what happened. New types may be added, so treat an unknown type as a general alert.

Alert types by group
GroupTypes
Ownershipregistered, re_registered, renewed, registrar_change, ns_change
Expirygrace_period_active, redemption_period_active, pending_delete, dropped, hold_released, registry_reserved
Marketlisted, delisted, price_change, marketplace_change, sold
Auctionsauction_started, auction_ending_soon, auction_ended, auction_sold, auction_no_sale

Check the signature

Sign the raw body, not a re-encoded copy. Reject a timestamp older than five minutes so an old request cannot be replayed.

Node.js
import crypto from 'node:crypto'

// rawBody: the request body exactly as received (a string, before JSON.parse)
function verify(rawBody, headers, secret) {
  const ts = headers['x-dtb-timestamp']
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false // older than 5 min
  const want = 'sha256=' + crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex')
  const got = String(headers['x-dtb-signature'] || '')
  return got.length === want.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(want))
}
Python
import hashlib, hmac, time

def verify(raw_body: bytes, headers, secret: str) -> bool:
    ts = headers["X-DTB-Timestamp"]
    if abs(time.time() - int(ts)) > 300:  # older than 5 min
        return False
    want = "sha256=" + hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(headers.get("X-DTB-Signature", ""), want)

Answer quickly

Answer with any 2xx status within 5 seconds. Do slow work after you answer. We do not follow redirects, so a 3xx counts as a failure.

Retries and switching off

A failed delivery is tried again 3 more times over about an hour. After 20 failed tries in a row the webhook switches off and we email you; fix your server, then switch it back on in Settings.

Address rules

  • HTTPS only, with a public address. Private, local and reserved addresses are refused.
  • No user name or password in the URL; 500 characters at most.
  • Test sends are limited to 5 every 10 minutes.