Alfa API
Quickstart
In about 15 minutes you will create an ATS job in Alfa, receive a signed test webhook and check its signature. Everything here runs on the development API, https://dev-api.welovealfa.com, with a test key.
Before you start
- An Alfa account in which you are an org admin or owner. Only they can generate API keys.
- The Alfa API switched on for your company. If a request answers
403api_not_enabled, contact Alfa to switch it on. curl, Node.js 18 or later, or Python 3 with therequestspackage.- A public HTTPS URL you control that shows the requests it receives, such as a request-inspection service or a tunnel to a server on your machine.
1. Generate a test key
- In Alfa, open Settings › Developers and choose Generate key.
- Name the key, for example
Quickstart, and choose the Test environment. - Keep the default scopes (
ats_jobs:write,jobs:readandcandidates:read) and addwebhooks:manage. - Copy the key. It starts
alfa_test_and is shown only once.
2. Put the key in an environment variable
export ALFA_API_KEY="alfa_test_…" # paste your whole key
Every sample below reads ALFA_API_KEY. A test key works only on https://dev-api.welovealfa.com; on the production host it is refused with 401.
The Node samples use await at the top level: save each one as a file ending .mjs and run it with node. Run the Python samples with python3 after pip install requests.
3. Create an ATS job
Send a job under your own id, here quickstart-001. Sending the same id again updates the job, so this step is safe to repeat.
# Platform keys only: add -H "Alfa-Company-Id: org_2abcDEF123"
curl -X PUT "https://dev-api.welovealfa.com/v1/integration/ats-jobs/quickstart-001" \
-H "Authorization: Bearer $ALFA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Quickstart test job","status":"open","location":"London, UK","department":"Engineering"}'The response is the job, with externalJobId quickstart-001 and status open. If you get 409 ats_integration_conflict, your company already uses another ATS integration in Alfa; contact Alfa before you continue.
4. List your ATS jobs
# Platform keys only: add -H "Alfa-Company-Id: org_2abcDEF123"
curl -X GET "https://dev-api.welovealfa.com/v1/integration/ats-jobs/page" \
-H "Authorization: Bearer $ALFA_API_KEY"The job you created is in data. nextCursor is null when there are no more pages.
5. Set a webhook endpoint
Replace the URL with your HTTPS request-inspection URL. Alfa refuses URLs that are not HTTPS or that point at a private network address.
# Platform keys only: add -H "Alfa-Company-Id: org_2abcDEF123"
curl -X PUT "https://dev-api.welovealfa.com/v1/integration/webhook-endpoint" \
-H "Authorization: Bearer $ALFA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://your-inspection-url.example/alfa"}'The first time you set an endpoint, the response includes its signing secret, which starts whsec_. It is shown only now, so save it:
export ALFA_WEBHOOK_SECRET="whsec_…" # paste the whole secret
If this key already had an endpoint, the request changes its URL and keeps its secret, so the response has no secret. To get a new secret, rotate it. The old secret stops working at once, so anything still verifying with it fails from then on:
# Platform keys only: add -H "Alfa-Company-Id: org_2abcDEF123"
curl -X POST "https://dev-api.welovealfa.com/v1/integration/webhook-endpoint/rotate-secret" \
-H "Authorization: Bearer $ALFA_API_KEY"Export the new secret from that response as ALFA_WEBHOOK_SECRET, replacing the old value, before you go on.
6. Send a test event
# Platform keys only: add -H "Alfa-Company-Id: org_2abcDEF123"
curl -X POST "https://dev-api.welovealfa.com/v1/integration/webhook-endpoint/test" \
-H "Authorization: Bearer $ALFA_API_KEY"The response shows the outcome: delivered is true when your URL answered with a 2xx status, with its statusCode and durationMs. Your inspection URL now shows a POST whose body has type webhook.test and a sample candidate, and whose headers include Alfa-Signature.
7. Verify the signature
- Save the raw request body exactly as received, byte for byte, as
body.json. Take it from your inspection tool's raw or download view rather than a formatted one, do not reformat it, and do not let your editor add a final newline. - Copy the
Alfa-Signatureheader value:export ALFA_SIGNATURE="t=…,v1=…" - Save the code below as
verify.cjs(it usesrequire, so not.mjs) orverify.py, then runnode verify.cjsorpython3 verify.py. It printstrue(orTrue).
const crypto = require('node:crypto');
function verifyAlfaSignature(secret, rawBody, signatureHeader, toleranceSeconds = 300) {
if (typeof signatureHeader !== 'string') {
return false;
}
const parts = {};
for (const part of signatureHeader.split(',')) {
const separator = part.indexOf('=');
if (separator > 0) {
parts[part.slice(0, separator).trim()] = part.slice(separator + 1).trim();
}
}
if (!/^\d+$/.test(parts.t || '') || !parts.v1) {
return false;
}
const nowSeconds = Math.floor(Date.now() / 1000);
if (Math.abs(nowSeconds - Number(parts.t)) > toleranceSeconds) {
return false;
}
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex');
const expectedBytes = Buffer.from(expected, 'utf8');
const receivedBytes = Buffer.from(parts.v1, 'utf8');
if (expectedBytes.length !== receivedBytes.length) {
return false;
}
return crypto.timingSafeEqual(expectedBytes, receivedBytes);
}
// Check the test event you saved. The tolerance is an hour because you are
// checking by hand; your webhook receiver should keep the 5-minute default.
const fs = require('node:fs');
const rawBody = fs.readFileSync('body.json', 'utf8');
console.log(
verifyAlfaSignature(process.env.ALFA_WEBHOOK_SECRET, rawBody, process.env.ALFA_SIGNATURE, 3600)
);
If it prints false, check that the secret and header are complete and that the body file matches what was sent. The webhooks guide explains the signature in full.
8. Link an Alfa job and read candidates
In Alfa, open a job's job form and choose Quickstart test job in the ATS job picker. From then on, candidates on that Alfa job are reported against your job id. Check the link:
# Platform keys only: add -H "Alfa-Company-Id: org_2abcDEF123"
curl -X GET "https://dev-api.welovealfa.com/v1/integration/jobs/page?externalJobId=quickstart-001" \
-H "Authorization: Bearer $ALFA_API_KEY"Then read candidates whose status changed since a point in time, oldest change first. The list covers every linked job of the company, so filter it with the jobId from the response above, and set updatedSince to when you started. It is empty until Alfa has screened or sourced someone for the job; each one also arrives at your webhook endpoint as a candidate.screened event.
# Platform keys only: add -H "Alfa-Company-Id: org_2abcDEF123"
curl -X GET "https://dev-api.welovealfa.com/v1/integration/candidates/page?updatedSince=2026-10-07T00%3A00%3A00Z&jobId=9301" \
-H "Authorization: Bearer $ALFA_API_KEY"9. Clean up
Close the test job by sending its title again with status closed; details you leave out, such as the location, are kept. It leaves the ATS job picker; Alfa jobs already linked to it stay linked.
# Platform keys only: add -H "Alfa-Company-Id: org_2abcDEF123"
curl -X PUT "https://dev-api.welovealfa.com/v1/integration/ats-jobs/quickstart-001" \
-H "Authorization: Bearer $ALFA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Quickstart test job","status":"closed"}'Remove the webhook endpoint so your inspection URL stops receiving events, and revoke the test key in Settings › Developers when you no longer need it.
# Platform keys only: add -H "Alfa-Company-Id: org_2abcDEF123"
curl -X DELETE "https://dev-api.welovealfa.com/v1/integration/webhook-endpoint" \
-H "Authorization: Bearer $ALFA_API_KEY"Next steps
- Authentication: live keys, scopes, rotation and storing keys safely.
- Webhooks: events, retries and the delivery log.
- API reference and the changelog.
