IAMScouting docs

IAMScouting Webhooks (v0.7)

Webhooks let partner systems (clubs, federations, integration platforms) subscribe

to event push from IAMScouting instead of polling our REST API.

per client; talk to <start@iamscouting.com> to enable.

/account/webhooks.

you verify against a per-endpoint secret.

minutes; endpoints that fail 20+ deliveries get auto-disabled.

---

1. Set up an endpoint

  1. Sign in with an Enterprise account.
  2. Open <https://iamscouting.com/account/webhooks>.
  3. Click + New endpoint, fill in:

- Label — for your own bookkeeping. Shown on the dashboard.

- Target URL — must be HTTPS. Plain HTTP is rejected.

- Events — one or more from the table below.

  1. After Create endpoint, copy the secret that's shown once. Store it

somewhere safe (vault, secret manager). You won't see it again — to rotate,

delete the endpoint and create a new one.

---

2. Event reference

All payloads are JSON. Field types are stable; we add new fields without bumping

a version but never rename or remove existing fields without 30-day notice.

| Event | When |

| ---------------------------- | ----------------------------------------------------- |

| lead.created | A verified agent published a new lead. |

| lead.closed | A lead was confirmed closed (deal done). |

| lead.interest_expressed | A club/scout expressed interest in a lead. |

| job.posted | A new job posting went live. |

| job.application_submitted | Someone applied to a job posting. |

| agent.verified | An agent license was approved by IAMScouting admin. |

lead.created

{
  "lead_id": 1234,
  "agent_user_id": "uuid",
  "player_name": "Lamine Yamal",
  "player_slug": "lamine-yamal",
  "position": "RW",
  "age": 19,
  "current_club": "FC Barcelona",
  "ask_price_min": 80000000,
  "ask_price_max": 120000000,
  "exclusivity": "exclusive",
  "visibility": "pro",
  "created_at": "2026-05-21T11:23:00.000Z"
}

lead.closed

{
  "lead_id": 1234,
  "agent_user_id": "uuid",
  "confirmed_by_user_id": "uuid",
  "player_name": "Lamine Yamal",
  "deal_value_cents": 9500000000,
  "closed_at": "2026-05-21T11:23:00.000Z"
}

lead.interest_expressed

{
  "lead_id": 1234,
  "agent_user_id": "uuid",
  "interested_user_id": "uuid",
  "player_name": "Lamine Yamal",
  "note": "Interested for centre-forward role.",
  "dm_thread_id": "uuid-or-null"
}

job.posted

{
  "job_id": 567,
  "poster_user_id": "uuid",
  "club_name": "Bayer Leverkusen",
  "role_title": "scout",
  "role_type": "full_time",
  "location": "Leverkusen",
  "country": "Germany",
  "remote_ok": false,
  "salary_min": 50000,
  "salary_max": 70000,
  "visibility": "public",
  "created_at": "2026-05-21T11:23:00.000Z"
}

job.application_submitted

{
  "job_id": 567,
  "poster_user_id": "uuid",
  "applicant_user_id": "uuid",
  "application_id": 4321,
  "role_title": "scout",
  "club_name": "Bayer Leverkusen"
}

agent.verified

{
  "agent_user_id": "uuid",
  "verified_at": "2026-05-21T11:23:00.000Z",
  "approved_by_email": "start@iamscouting.com"
}

---

3. Headers

Every request includes:

| Header | Description |

| ------------------- | ---------------------------------------------------- |

| X-IAMS-Signature | sha256=<hex> HMAC-SHA256 of the raw request body. |

| X-IAMS-Event | Event type, e.g. lead.created. |

| X-IAMS-Delivery | Numeric delivery id. Use for idempotency dedup. |

| User-Agent | iamscouting-webhooks/0.7 |

---

4. Verify the signature (Node.js)

import { createHmac, timingSafeEqual } from 'node:crypto';

const SECRET = process.env.IAMS_WEBHOOK_SECRET; // store the secret in env

function verifyIamsSignature(rawBody, header) {
  if (!header || !header.startsWith('sha256=')) return false;
  const expected = createHmac('sha256', SECRET).update(rawBody).digest('hex');
  const got = header.slice('sha256='.length);
  if (expected.length !== got.length) return false;
  return timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(got, 'hex'));
}

Always sign the raw body, not a re-serialised parse — JSON whitespace

differences will break the HMAC.

Python

import hmac, hashlib

SECRET = b'<copy-from-dashboard>'

def verify_iams_signature(raw_body: bytes, header: str) -> bool:
    if not header or not header.startswith('sha256='):
        return False
    expected = hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
    got = header[len('sha256='):]
    return hmac.compare_digest(expected, got)

---

5. Retry semantics

but ignored on my side" — that ends the retry loop.

multiple times if your endpoint times out after partially processing.

202 quickly and process asynchronously.

Auto-disable

If an endpoint accumulates more than 20 failed deliveries (across distinct

events), we set active = false automatically and stop sending. Re-enable from

the dashboard once you've fixed the issue; we reset the failure counter on

re-enable.

---

6. FAQ

*Can I subscribe to events for all users on the platform?*

No. Webhook subscriptions are scoped to your account — you receive only events

your account is entitled to see (this matches API key semantics). For

federation-wide event firehoses, contact us directly.

Can I rotate the secret?

Delete the endpoint and create a new one. There's no in-place rotation by

design — it forces a clean cutover on the partner side.

What if I miss events while my endpoint is down?

Once an event lands as failed (5 attempts exhausted), it's not retried. Use

the REST API (/api/v1/*) to backfill missed state during outages.