ATS integration
Feed matched candidates into your existing ATS
InTransparency doesn't replace your ATS — it feeds it. When a high-confidence match fires, we send a signed webhook; a 30-line relay turns that into a Greenhouse prospect, a Lever opportunity, or a Workday requisition candidate.
The pattern
InTransparency fires a webhook → your tiny relay verifies the signature → your relay calls your ATS's candidates endpoint. No custom middleware, no polling, no scheduled sync. The relay runs anywhere (Vercel, Cloudflare Workers, AWS Lambda, or a FastAPI container).
Create an agent key and register a webhook
InTransparency API
# Step 1: Create an InTransparency agent key with webhook scope curl -X POST https://www.in-transparency.com/api/agents/keys \ -H "Cookie: <your session>" \ -H "Content-Type: application/json" \ -d '{"name":"ATS-relay","scopes":["agent:webhook"]}' # Response: { "key": "ita_...", ... } — save it once; never shown again # Step 2: Register your webhook endpoint with InTransparency curl -X POST https://www.in-transparency.com/api/agents/webhooks \ -H "Authorization: Bearer ita_..." \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-relay.example.com/webhooks/intransparency", "events": ["match.created", "credential.issued"] }' # Response includes { "secret": "..." } — store as INTRANSPARENCY_WEBHOOK_SECRETGreenhouse relay (Node / TypeScript)
Lever relay (Python / FastAPI)
Greenhouse Harvest API
// Deno / Node webhook relay: InTransparency → Greenhouse Harvest API // Env: GREENHOUSE_API_KEY, GREENHOUSE_USER_ID, GREENHOUSE_JOB_ID, // INTRANSPARENCY_WEBHOOK_SECRET import crypto from 'node:crypto' export default async function relay(req: Request): Promise<Response> { // 1. Verify the signature (constant-time compare) const body = await req.text() const sig = req.headers.get('X-InTransparency-Signature') || '' const expected = 'sha256=' + crypto .createHmac('sha256', process.env.INTRANSPARENCY_WEBHOOK_SECRET!) .update(body).digest('hex') const ok = sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)) if (!ok) return new Response('bad signature', { status: 401 }) const { event, data } = JSON.parse(body) if (event !== 'match.created' || data.matchScore < 70) { return new Response('ignored', { status: 200 }) } // 2. Create a prospect in Greenhouse. Before the student accepts contact, // the webhook carries no email or surname — only a first name with an // initial ("Giulia M."), a stable pseudonym and the evidence link. The // recruiter opens the link, writes to the student on InTransparency, and // the full contact arrives once the student says yes. const [firstName, ...rest] = String(data.candidateName).split(' ') await fetch('https://harvest.greenhouse.io/v1/candidates', { method: 'POST', headers: { 'Authorization': 'Basic ' + Buffer.from(process.env.GREENHOUSE_API_KEY + ':').toString('base64'), 'Content-Type': 'application/json', 'On-Behalf-Of': process.env.GREENHOUSE_USER_ID!, }, body: JSON.stringify({ first_name: firstName, last_name: rest.join(' ') || data.candidatePseudonym, applications: [{ job_id: Number(process.env.GREENHOUSE_JOB_ID) }], website_addresses: [{ value: data.url, type: 'other' }], tags: ['InTransparency', data.decisionLabel], custom_fields: [ { name_key: 'intransparency_match_id', value: data.matchId }, { name_key: 'intransparency_match_score', value: data.matchScore }, ], }), }) return new Response('ok', { status: 200 }) }Lever Opportunities API
# Python / FastAPI webhook relay: InTransparency → Lever API # Env: LEVER_API_KEY, INTRANSPARENCY_WEBHOOK_SECRET, LEVER_POSTING_ID import hashlib, hmac, os, json, httpx from fastapi import FastAPI, Request, HTTPException app = FastAPI() @app.post("/webhooks/intransparency") async def relay(req: Request): body = await req.body() sig = req.headers.get("X-InTransparency-Signature", "") expected = "sha256=" + hmac.new( os.environ["INTRANSPARENCY_WEBHOOK_SECRET"].encode(), body, hashlib.sha256, ).hexdigest() if not hmac.compare_digest(sig, expected): raise HTTPException(401, "bad signature") payload = json.loads(body) if payload["event"] != "match.created": return {"ok": True} data = payload["data"] if data["matchScore"] < 70: return {"ok": True} # Lever: create an opportunity (candidate attached to a posting) async with httpx.AsyncClient(auth=(os.environ["LEVER_API_KEY"], "")) as client: r = await client.post( "https://api.lever.co/v1/opportunities", json={ "name": data["candidateName"], "origin": "sourced", "sources": ["InTransparency"], "postings": [os.environ["LEVER_POSTING_ID"]], "tags": [f"intransparency-score-{data['matchScore']}"], "links": [data["url"]], }, ) r.raise_for_status() return {"ok": True}
Other ATS platforms
Workday Recruiting
Use the Recruiting Staffing Web Service (SOAP) — add a Candidate with Add_Candidate operation. Same relay pattern, different payload.
SmartRecruiters
POST to /candidates with jobId and status=LEAD. Custom fields map cleanly.
Recruitee
POST to /candidates with placements=[{offer_id}]. Attach evidence URL as note.
Teamtailor
First-party push is live in beta — add a TeamTailor API key in recruiter settings. Building your own instead: POST to /v1/candidates with included job-applications; sourced candidates appear in the pipeline as leads.
Personio
Use the Recruiting API — POST /v1/recruiting/applications. Italian HR teams already familiar with Personio can wire this up in under an hour.
Ashby
First-party push is live in beta — add an Ashby API key in recruiter settings and skip the relay. Building your own instead: POST to /candidate.create, then /candidate.addApplication.