Growth.Talent

Webhooks

Subscribe a URL to domain events. Growth.Talent POSTs JSON-encoded events to your endpoint with an HMAC-SHA256 signature. Replaces polling for ATSes, sourcing bots, lifecycle systems, and any agent that needs to react in real time.

Available events

EventWhen it firesVisible to
application.receivedA candidate submitted an application to one of your company's jobs.Company
application.createdYou (the candidate) submitted an application — useful for personal pipeline trackers.Candidate
job.approvedYour job posting passed AI moderation and is live.Company
job.expiredYour job posting was closed or hit its expiry date.Company
candidate.verifiedAdmin verified your candidate profile.Candidate
boost.purchasedYour company purchased a Boost (featured placement).Company

Subscribe

Mint an API key with the webhooks:write scope at /settings/api-keys, then:

terminal
curl -X POST https://www.growthtalent.org/api/v1/webhooks \
  -H "Authorization: Bearer gt_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/webhooks/growthtalent",
    "events": ["application.received", "job.approved"],
    "description": "ATS prod"
  }'

The response includes a secret — store it. You won't see it again.

Webhook URLs must be https. Keep the secret out of logs and source control.

Verify signatures

Every delivery sets X-GrowthTalent-Signature and X-GrowthTalent-Timestamp headers. The signature has the form t=<timestamp>,v1=<hex hmac> where the HMAC-SHA256 covers `${timestamp}.${rawBody}`.

verify.ts (Node)
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyWebhook(
  rawBody: string,
  header: string,
  secret: string,
  toleranceSec = 300,
): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (!parts.t || !parts.v1) return false;
  const ts = parseInt(parts.t, 10);
  if (Math.abs(Math.floor(Date.now() / 1000) - ts) > toleranceSec) return false;

  const expected = createHmac("sha256", secret)
    .update(`${ts}.${rawBody}`)
    .digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(parts.v1, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}
verify.py
import hmac, hashlib, time

def verify_webhook(raw_body: bytes, header: str, secret: str, tolerance_sec: int = 300) -> bool:
    parts = dict(p.split("=") for p in header.split(","))
    if "t" not in parts or "v1" not in parts:
        return False
    ts = int(parts["t"])
    if abs(int(time.time()) - ts) > tolerance_sec:
        return False
    expected = hmac.new(
        secret.encode(),
        f"{ts}.{raw_body.decode()}".encode(),
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Retry policy

Non-2xx responses (or timeouts > 10s) get retried with exponential backoff: 1 minute, 5 minutes, 15 minutes, 1 hour, 6 hours. After 6 failed attempts the delivery is marked DEAD and dropped. The subscription stays active.

Inspect recent deliveries:

curl https://www.growthtalent.org/api/v1/webhooks/<id>/deliveries \
  -H "Authorization: Bearer gt_live_..."

Manage subscriptions

# List your subscriptions
curl https://www.growthtalent.org/api/v1/webhooks \
  -H "Authorization: Bearer gt_live_..."

# Revoke
curl -X DELETE https://www.growthtalent.org/api/v1/webhooks/<id> \
  -H "Authorization: Bearer gt_live_..."

Example payload

application.received
{
  "event": "application.received",
  "eventId": "a3f1c8d29e6b1042",
  "application": {
    "id": "clw3xyz...",
    "appliedVia": "magic",
    "message": "Hi — built growth at Spendesk #4 to unicorn.",
    "hadMessage": true,
    "source": "api",
    "createdAt": "2026-05-06T22:13:39.123Z"
  },
  "job": {
    "id": "clw...",
    "slug": "head-of-growth-the-mobile-first-company",
    "title": "Head of Growth",
    "category": "growth-marketing",
    "market": "USA"
  },
  "candidate": {
    "id": "clw...",
    "slug": "jane-doe",
    "name": "Jane Doe",
    "currentTitle": "Growth Lead",
    "linkedinUrl": "https://www.linkedin.com/in/jane-doe"
  },
  "company": {
    "id": "clw...",
    "slug": "the-mobile-first-company",
    "name": "The Mobile First Company"
  }
}