****************************** Track Applicant Webhooks ****************************** When a candidate applies to a track and is added to the Applied stage with ``invite_status`` set to ``Completed``, Xobin sends a signed ``POST`` request to your configured webhook URL. This event fires for all application paths, including: - Public apply form submissions - Invited candidates completing the apply form - Direct API applicant creation - Bulk CSV/Naukri uploads - Resume ingestion flows Event ----- - **Event name:** ``tracks.applicant.completed`` - **Header:** ``x-xobin-event: tracks.applicant.completed`` Configuration ------------- There are two configuration APIs: .. list-table:: :header-rows: 1 :widths: 30 35 35 * - API - Scope - Use it when * - Track Webhook Settings - Company-wide default - You want one webhook URL for all tracks unless a track overrides it. * - Track Webhook - One specific track - One track should use a different URL, secret, or enabled status. Recommended setup: 1. Configure the company-wide default with ``POST /tracks/settings/webhooks``. 2. For tracks that need custom behavior, configure an override with ``PUT /tracks//webhook``. 3. Use ``GET /tracks//webhook`` to confirm the ``effective`` config Xobin will use for that track. Resolution order: 1. Per-track override when present and ``inherit`` is ``false`` 2. Company default otherwise 3. No delivery when the effective config is disabled or has no URL Example: company default ------------------------ .. code-block:: JSON { "webhook": { "status": true, "url": "https://example.com/xobin/default-track-webhook", "secret": "company-wide-signing-secret" } } Example: per-track override --------------------------- .. code-block:: JSON { "webhook": { "status": true, "url": "https://example.com/xobin/backend-engineer-webhook", "secret": "track-specific-signing-secret", "inherit": false } } Example: make a track use company default ----------------------------------------- .. code-block:: JSON { "webhook": { "inherit": true } } Sample payload -------------- .. code-block:: JSON { "event": "tracks.applicant.completed", "event_id": "550e8400-e29b-41d4-a716-446655440000", "occurred_at": "2026-07-06T10:30:00+00:00", "company_id": 123, "track": { "id": 456, "title": "Backend Engineer", "job_details": {} }, "stage": { "stage_id": "65a1b2c3d4e5f6789abcdef0", "name": "Applied", "type": "job", "color": "#4CAF50", "source_id": null }, "applicant": { "application_id": 789, "candidate_id": 101, "name": "Jane Candidate", "email": "jane.candidate@example.com", "invite_status": "Completed", "fit_score": 82, "fit_reason": "Strong match for Python, API design, and backend experience.", "parsed_resume": { "name": "Jane Candidate", "email": "jane.candidate@example.com", "skills": ["Python", "Flask", "MySQL"], "fitment_score": 82, "fitment_reason": "Strong match for Python, API design, and backend experience." }, "fields": [ { "id": "field-id-1", "label": "Phone", "type": "text", "value": "+1 555 0100" }, { "id": "field-id-2", "label": "Resume", "type": "media", "value": "https://storage.googleapis.com/signed-resume-url", "is_resume": true, "fit_score": 82, "fit_reason": "Strong match for Python, API design, and backend experience.", "parsed_resume": { "name": "Jane Candidate", "email": "jane.candidate@example.com", "skills": ["Python", "Flask", "MySQL"], "fitment_score": 82, "fitment_reason": "Strong match for Python, API design, and backend experience." } } ] }, "source": "public_apply" } Media and resume fields ----------------------- - For fields with type ``media``, ``video``, ``snapshot``, or ``file``, Xobin returns a signed URL when the stored value is a Google Cloud Storage URL. - For resume fields, Xobin includes ``parsed_resume``, ``fit_score``, and ``fit_reason`` when those values are available for the applicant. - Resume enrichment is included both on the resume field and on the top-level ``applicant`` object for easier consumption. Possible ``source`` values: - ``api_v2`` - ``public_apply`` - ``invite_completion`` - ``bulk_upload`` - ``resume_ingestion`` Authenticate webhook payload ----------------------------- Each incoming ``POST`` request includes: - ``x-xobin-signature``: HMAC-SHA256 signature of the raw JSON body - ``x-xobin-event``: ``tracks.applicant.completed`` - ``x-company-id``: company identifier If you configured a custom ``secret`` in the webhook settings, use that value for verification. Otherwise use your company API key. To validate the request: 1. Take the raw request body bytes. 2. Compute ``HMAC-SHA256`` using your signing secret. 3. Base64 URL-safe encode the digest without padding. 4. Compare the result with ``x-xobin-signature``. .. tab:: Python .. code-block:: python :linenos: import hmac import hashlib import base64 def verify_webhook(data, hmac_header, secret): digest = hmac.new( secret.encode("utf-8"), data.encode("utf-8"), digestmod=hashlib.sha256, ).digest() computed_hmac = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii") return hmac.compare_digest(computed_hmac, hmac_header) Delivery semantics ------------------ - Delivery is best-effort and does not block applicant creation. - Failed deliveries are logged for observability. - Applicant creation succeeds even if the webhook delivery fails.