LogoDecisionMaker.Email

Webhooks

Signed HTTP POST when a search completes or fails.

Subscribe to search.completed and search.failed events to skip polling. Every delivery is signed with HMAC-SHA256 so you can verify it came from us.

Setting up an endpoint

Open the API Keys dashboard, scroll to the Webhooks card, click Add endpoint, and enter your URL. You will see the signing secret once. Store it in your server's env vars.

Your endpoint must return a 2xx response within 10 seconds. Non-2xx or timeout triggers a retry.

Payload shape

{
  "event": "search.completed",
  "timestamp": 1776709919,
  "data": {
    "searchId": "k1710dek6438k67zhfxqt9ay3d8576da",
    "teamId": "kx76fgaysm8b9kqsgjczj8ys9d839b6y",
    "domain": "acme.com",
    "role": "CEO",
    "status": "completed",
    "results": [
      {
        "email": "jane@acme.com",
        "name": "Jane Doe",
        "title": "Chief Executive Officer",
        "score": 95,
        "source": "verified"
      }
    ]
  }
}

Headers

HeaderExamplePurpose
X-DME-Eventsearch.completedWhich event fired.
X-DME-Deliverymn776zwave3z9p7ncdcta4ywph856e9mUnique ID for this delivery attempt. Idempotency key.
X-DME-Signaturet=1776709919,v1=982232...Timestamp and HMAC-SHA256 hex signature.
Content-Typeapplication/json
User-Agentdecisionmaker.email-webhooks/1.0

Verifying the signature

The signing input is <timestamp>\n<raw-body>. Compute HMAC-SHA256 with your secret and compare with a timing-safe equal.

Node

import crypto from "node:crypto";

export function verify(req, secret) {
  const header = req.headers["x-dme-signature"]; // "t=...,v1=..."
  const parts = Object.fromEntries(
    header.split(",").map((kv) => kv.split("=")),
  );
  const signingInput = `${parts.t}\n${req.rawBody}`;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(signingInput)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(parts.v1),
  );
}

Python

import hmac, hashlib

def verify(headers, raw_body, secret):
    header = headers["x-dme-signature"]  # "t=...,v1=..."
    parts = dict(kv.split("=", 1) for kv in header.split(","))
    signing_input = f"{parts['t']}\n{raw_body}"
    expected = hmac.new(
        secret.encode(),
        signing_input.encode(),
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Always use the raw request body, not a re-serialized JSON object. Any whitespace difference will break the signature.

Retries

Failed deliveries retry with exponential backoff:

AttemptDelay
260s
3120s
4240s
5480s
6960s

After 6 attempts, the delivery is marked failed and dropped.

Auto-disable

If an endpoint accumulates 20 consecutive failed deliveries, it is automatically disabled. Delete and recreate to re-enable.

Replay protection

The timestamp is part of the signing input, so an attacker cannot replay an old payload with a new timestamp. Still, reject deliveries with a timestamp older than 5 minutes on your side to limit the replay window if a secret leaks.

On this page