IAMScouting Webhooks (v0.7)
Webhooks let partner systems (clubs, federations, integration platforms) subscribe
to event push from IAMScouting instead of polling our REST API.
- Enterprise add-on. Same gate as API keys (
/account/api-keys). Provisioned
per client; talk to <start@iamscouting.com> to enable.
- Self-service config. Manage endpoints from
- HMAC-signed payloads. Every request carries an
X-IAMS-Signatureheader
you verify against a per-endpoint secret.
- At-least-once delivery. Failed deliveries retry up to 5 times across ~5
minutes; endpoints that fail 20+ deliveries get auto-disabled.
---
1. Set up an endpoint
- Sign in with an Enterprise account.
- Open <https://iamscouting.com/account/webhooks>.
- 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.
- 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
- We retry up to 5 attempts per event. Anything 2xx is success.
- 4xx and 5xx both count as a failure. Use 2xx-with-empty-body for "received
but ignored on my side" — that ends the retry loop.
- Retries are spaced ~1 minute apart (the worker runs every minute).
- Use
X-IAMS-Deliveryfor idempotency — the same delivery id can arrive
multiple times if your endpoint times out after partially processing.
- A timeout is 8 seconds. If your endpoint legitimately needs longer, return
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.