d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
358 lines
15 KiB
TypeScript
358 lines
15 KiB
TypeScript
import type { SubBlockConfig } from '@/blocks/types'
|
|
import type { TriggerOutput } from '@/triggers/types'
|
|
|
|
/**
|
|
* Top-level ids mirrored from the webhook JSON for ergonomics (see Greenhouse webhook common attributes).
|
|
* Always present on `formatInput`; use null when not applicable to the event.
|
|
*/
|
|
const greenhouseIndexedOutputs = {
|
|
applicationId: {
|
|
type: 'number',
|
|
description:
|
|
'Application id when present (`payload.application.id` or flat `payload.application_id` on offers)',
|
|
},
|
|
candidateId: {
|
|
type: 'number',
|
|
description: 'Candidate id when `payload.application.candidate.id` is present',
|
|
},
|
|
jobId: {
|
|
type: 'number',
|
|
description: 'Job id from `payload.job.id` or flat `payload.job_id` when present',
|
|
},
|
|
} as const
|
|
|
|
/**
|
|
* Dropdown options for the Greenhouse trigger type selector.
|
|
*/
|
|
export const greenhouseTriggerOptions = [
|
|
{ label: 'Candidate Hired', id: 'greenhouse_candidate_hired' },
|
|
{ label: 'New Application', id: 'greenhouse_new_application' },
|
|
{ label: 'Candidate Stage Change', id: 'greenhouse_candidate_stage_change' },
|
|
{ label: 'Candidate Rejected', id: 'greenhouse_candidate_rejected' },
|
|
{ label: 'Offer Created', id: 'greenhouse_offer_created' },
|
|
{ label: 'Job Created', id: 'greenhouse_job_created' },
|
|
{ label: 'Job Updated', id: 'greenhouse_job_updated' },
|
|
{ label: 'All configured Greenhouse events', id: 'greenhouse_webhook' },
|
|
]
|
|
|
|
/**
|
|
* Maps trigger IDs to Greenhouse webhook action strings.
|
|
* Used for event filtering in the webhook processor.
|
|
*/
|
|
export const GREENHOUSE_EVENT_MAP: Record<string, string> = {
|
|
greenhouse_candidate_hired: 'hire_candidate',
|
|
greenhouse_new_application: 'new_candidate_application',
|
|
greenhouse_candidate_stage_change: 'candidate_stage_change',
|
|
greenhouse_candidate_rejected: 'reject_candidate',
|
|
greenhouse_offer_created: 'offer_created',
|
|
greenhouse_job_created: 'job_created',
|
|
greenhouse_job_updated: 'job_updated',
|
|
}
|
|
|
|
/**
|
|
* Checks whether a Greenhouse webhook payload matches the configured trigger.
|
|
*/
|
|
export function isGreenhouseEventMatch(triggerId: string, action: string): boolean {
|
|
const expectedAction = GREENHOUSE_EVENT_MAP[triggerId]
|
|
if (expectedAction === undefined) {
|
|
return false
|
|
}
|
|
return action === expectedAction
|
|
}
|
|
|
|
/**
|
|
* Builds extra fields for Greenhouse triggers.
|
|
* Includes an optional secret key for HMAC signature verification.
|
|
*/
|
|
export function buildGreenhouseExtraFields(triggerId: string): SubBlockConfig[] {
|
|
return [
|
|
{
|
|
id: 'secretKey',
|
|
title: 'Secret Key (Optional)',
|
|
type: 'short-input',
|
|
placeholder: 'Enter the same secret key configured in Greenhouse',
|
|
description:
|
|
'When set, requests must include a valid Signature header (HMAC-SHA256). If left empty, the endpoint does not verify signatures—only use on a private URL you fully control.',
|
|
password: true,
|
|
mode: 'trigger',
|
|
condition: { field: 'selectedTriggerId', value: triggerId },
|
|
},
|
|
]
|
|
}
|
|
|
|
function buildSourceOutputs(): Record<string, TriggerOutput> {
|
|
return {
|
|
id: { type: 'number', description: 'Source ID' },
|
|
name: { type: 'string', description: 'Source name when provided by Greenhouse' },
|
|
public_name: {
|
|
type: 'string',
|
|
description: 'Public-facing source name when provided by Greenhouse',
|
|
},
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates HTML setup instructions for Greenhouse webhooks.
|
|
* Webhooks are manually configured in the Greenhouse admin panel.
|
|
*/
|
|
export function greenhouseSetupInstructions(eventType: string): string {
|
|
const instructions = [
|
|
'Copy the <strong>Webhook URL</strong> above.',
|
|
'In Greenhouse, go to <strong>Configure > Dev Center > Webhooks</strong>.',
|
|
'Click <strong>Create New Webhook</strong>.',
|
|
'Paste the Webhook URL into the <strong>Endpoint URL</strong> field.',
|
|
'Enter a <strong>Secret Key</strong> for HMAC signature verification (recommended). Leave empty only if you accept unauthenticated POSTs to this URL.',
|
|
`Under <strong>When</strong>, select the appropriate <strong>${eventType}</strong>.`,
|
|
'Click <strong>Create Webhook</strong> to save.',
|
|
'Click "Save" above to activate your trigger.',
|
|
]
|
|
|
|
return instructions
|
|
.map(
|
|
(instruction, index) =>
|
|
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
|
|
)
|
|
.join('')
|
|
}
|
|
|
|
/**
|
|
* Build outputs for hire_candidate events.
|
|
* Greenhouse nests candidate inside application: payload.application.candidate
|
|
* Uses both singular `job` (deprecated) and `jobs` array.
|
|
*/
|
|
export function buildCandidateHiredOutputs(): Record<string, TriggerOutput> {
|
|
return {
|
|
action: { type: 'string', description: 'The webhook event type (hire_candidate)' },
|
|
...greenhouseIndexedOutputs,
|
|
payload: {
|
|
application: {
|
|
id: { type: 'number', description: 'Application ID' },
|
|
status: { type: 'string', description: 'Application status' },
|
|
prospect: { type: 'boolean', description: 'Whether the applicant is a prospect' },
|
|
applied_at: { type: 'string', description: 'When the application was submitted' },
|
|
url: { type: 'string', description: 'Application URL in Greenhouse' },
|
|
current_stage: {
|
|
id: { type: 'number', description: 'Current stage ID' },
|
|
name: { type: 'string', description: 'Current stage name' },
|
|
},
|
|
candidate: {
|
|
id: { type: 'number', description: 'Candidate ID' },
|
|
first_name: { type: 'string', description: 'First name' },
|
|
last_name: { type: 'string', description: 'Last name' },
|
|
title: { type: 'string', description: 'Current title' },
|
|
company: { type: 'string', description: 'Current company' },
|
|
email_addresses: { type: 'json', description: 'Email addresses' },
|
|
phone_numbers: { type: 'json', description: 'Phone numbers' },
|
|
recruiter: { type: 'json', description: 'Assigned recruiter' },
|
|
coordinator: { type: 'json', description: 'Assigned coordinator' },
|
|
},
|
|
jobs: { type: 'json', description: 'Associated jobs (array)' },
|
|
source: buildSourceOutputs(),
|
|
offer: {
|
|
id: { type: 'number', description: 'Offer ID' },
|
|
version: { type: 'number', description: 'Offer version' },
|
|
starts_at: { type: 'string', description: 'Offer start date' },
|
|
custom_fields: { type: 'json', description: 'Offer custom fields' },
|
|
},
|
|
custom_fields: { type: 'json', description: 'Application custom fields' },
|
|
},
|
|
},
|
|
} as Record<string, TriggerOutput>
|
|
}
|
|
|
|
/**
|
|
* Build outputs for new_candidate_application events.
|
|
* Candidate is nested inside application: payload.application.candidate
|
|
*/
|
|
export function buildNewApplicationOutputs(): Record<string, TriggerOutput> {
|
|
return {
|
|
action: { type: 'string', description: 'The webhook event type (new_candidate_application)' },
|
|
...greenhouseIndexedOutputs,
|
|
payload: {
|
|
application: {
|
|
id: { type: 'number', description: 'Application ID' },
|
|
status: { type: 'string', description: 'Application status' },
|
|
prospect: { type: 'boolean', description: 'Whether the applicant is a prospect' },
|
|
applied_at: { type: 'string', description: 'When the application was submitted' },
|
|
url: { type: 'string', description: 'Application URL in Greenhouse' },
|
|
current_stage: {
|
|
id: { type: 'number', description: 'Current stage ID' },
|
|
name: { type: 'string', description: 'Current stage name' },
|
|
},
|
|
candidate: {
|
|
id: { type: 'number', description: 'Candidate ID' },
|
|
first_name: { type: 'string', description: 'First name' },
|
|
last_name: { type: 'string', description: 'Last name' },
|
|
title: { type: 'string', description: 'Current title' },
|
|
company: { type: 'string', description: 'Current company' },
|
|
created_at: { type: 'string', description: 'When the candidate was created' },
|
|
email_addresses: { type: 'json', description: 'Email addresses' },
|
|
phone_numbers: { type: 'json', description: 'Phone numbers' },
|
|
tags: { type: 'json', description: 'Candidate tags' },
|
|
},
|
|
jobs: { type: 'json', description: 'Associated jobs (array)' },
|
|
source: buildSourceOutputs(),
|
|
answers: { type: 'json', description: 'Application question answers' },
|
|
attachments: { type: 'json', description: 'Application attachments' },
|
|
custom_fields: { type: 'json', description: 'Application custom fields' },
|
|
},
|
|
},
|
|
} as Record<string, TriggerOutput>
|
|
}
|
|
|
|
/**
|
|
* Build outputs for candidate_stage_change events.
|
|
* Candidate is nested inside application: payload.application.candidate
|
|
*/
|
|
export function buildCandidateStageChangeOutputs(): Record<string, TriggerOutput> {
|
|
return {
|
|
action: { type: 'string', description: 'The webhook event type (candidate_stage_change)' },
|
|
...greenhouseIndexedOutputs,
|
|
payload: {
|
|
application: {
|
|
id: { type: 'number', description: 'Application ID' },
|
|
status: { type: 'string', description: 'Application status' },
|
|
prospect: { type: 'boolean', description: 'Whether the applicant is a prospect' },
|
|
applied_at: { type: 'string', description: 'When the application was submitted' },
|
|
url: { type: 'string', description: 'Application URL in Greenhouse' },
|
|
current_stage: {
|
|
id: { type: 'number', description: 'Current stage ID' },
|
|
name: { type: 'string', description: 'Current stage name' },
|
|
interviews: { type: 'json', description: 'Interviews in this stage' },
|
|
},
|
|
candidate: {
|
|
id: { type: 'number', description: 'Candidate ID' },
|
|
first_name: { type: 'string', description: 'First name' },
|
|
last_name: { type: 'string', description: 'Last name' },
|
|
title: { type: 'string', description: 'Current title' },
|
|
company: { type: 'string', description: 'Current company' },
|
|
email_addresses: { type: 'json', description: 'Email addresses' },
|
|
phone_numbers: { type: 'json', description: 'Phone numbers' },
|
|
},
|
|
jobs: { type: 'json', description: 'Associated jobs (array)' },
|
|
source: buildSourceOutputs(),
|
|
custom_fields: { type: 'json', description: 'Application custom fields' },
|
|
},
|
|
},
|
|
} as Record<string, TriggerOutput>
|
|
}
|
|
|
|
/**
|
|
* Build outputs for reject_candidate events.
|
|
* Candidate is nested inside application: payload.application.candidate
|
|
*/
|
|
export function buildCandidateRejectedOutputs(): Record<string, TriggerOutput> {
|
|
return {
|
|
action: { type: 'string', description: 'The webhook event type (reject_candidate)' },
|
|
...greenhouseIndexedOutputs,
|
|
payload: {
|
|
application: {
|
|
id: { type: 'number', description: 'Application ID' },
|
|
status: { type: 'string', description: 'Application status (rejected)' },
|
|
prospect: { type: 'boolean', description: 'Whether the applicant is a prospect' },
|
|
applied_at: { type: 'string', description: 'When the application was submitted' },
|
|
rejected_at: { type: 'string', description: 'When the candidate was rejected' },
|
|
url: { type: 'string', description: 'Application URL in Greenhouse' },
|
|
current_stage: {
|
|
id: { type: 'number', description: 'Stage ID where rejected' },
|
|
name: { type: 'string', description: 'Stage name where rejected' },
|
|
},
|
|
candidate: {
|
|
id: { type: 'number', description: 'Candidate ID' },
|
|
first_name: { type: 'string', description: 'First name' },
|
|
last_name: { type: 'string', description: 'Last name' },
|
|
email_addresses: { type: 'json', description: 'Email addresses' },
|
|
phone_numbers: { type: 'json', description: 'Phone numbers' },
|
|
},
|
|
jobs: { type: 'json', description: 'Associated jobs (array)' },
|
|
rejection_reason: {
|
|
type: 'json',
|
|
description: 'Rejection reason object with id, name, and type fields',
|
|
},
|
|
rejection_details: { type: 'json', description: 'Rejection details with custom fields' },
|
|
custom_fields: { type: 'json', description: 'Application custom fields' },
|
|
},
|
|
},
|
|
} as Record<string, TriggerOutput>
|
|
}
|
|
|
|
/**
|
|
* Build outputs for offer_created events.
|
|
* Offer payload is flat under payload (not nested under payload.offer).
|
|
*/
|
|
export function buildOfferCreatedOutputs(): Record<string, TriggerOutput> {
|
|
return {
|
|
action: { type: 'string', description: 'The webhook event type (offer_created)' },
|
|
...greenhouseIndexedOutputs,
|
|
payload: {
|
|
id: { type: 'number', description: 'Offer ID' },
|
|
application_id: { type: 'number', description: 'Associated application ID' },
|
|
job_id: { type: 'number', description: 'Associated job ID' },
|
|
user_id: { type: 'number', description: 'User who created the offer' },
|
|
version: { type: 'number', description: 'Offer version number' },
|
|
sent_on: { type: 'string', description: 'When the offer was sent' },
|
|
resolved_at: { type: 'string', description: 'When the offer was resolved' },
|
|
start_date: { type: 'string', description: 'Offer start date' },
|
|
notes: { type: 'string', description: 'Offer notes' },
|
|
offer_status: { type: 'string', description: 'Offer status' },
|
|
custom_fields: { type: 'json', description: 'Custom field values' },
|
|
},
|
|
} as Record<string, TriggerOutput>
|
|
}
|
|
|
|
/**
|
|
* Shared job payload shape used by both job_created and job_updated events.
|
|
*/
|
|
function buildJobPayload(): Record<string, TriggerOutput> {
|
|
return {
|
|
id: { type: 'number', description: 'Job ID' },
|
|
name: { type: 'string', description: 'Job title' },
|
|
requisition_id: { type: 'string', description: 'Requisition ID' },
|
|
status: { type: 'string', description: 'Job status (open, closed, draft)' },
|
|
confidential: { type: 'boolean', description: 'Whether the job is confidential' },
|
|
created_at: { type: 'string', description: 'When the job was created' },
|
|
opened_at: { type: 'string', description: 'When the job was opened' },
|
|
closed_at: { type: 'string', description: 'When the job was closed' },
|
|
departments: { type: 'json', description: 'Associated departments' },
|
|
offices: { type: 'json', description: 'Associated offices' },
|
|
hiring_team: { type: 'json', description: 'Hiring team (managers, recruiters, etc.)' },
|
|
openings: { type: 'json', description: 'Job openings' },
|
|
custom_fields: { type: 'json', description: 'Custom field values' },
|
|
} as Record<string, TriggerOutput>
|
|
}
|
|
|
|
/**
|
|
* Build outputs for job_created events.
|
|
* Job data is nested under payload.job.
|
|
*/
|
|
export function buildJobCreatedOutputs(): Record<string, TriggerOutput> {
|
|
return {
|
|
action: { type: 'string', description: 'The webhook event type (job_created)' },
|
|
...greenhouseIndexedOutputs,
|
|
payload: { job: buildJobPayload() },
|
|
} as Record<string, TriggerOutput>
|
|
}
|
|
|
|
/**
|
|
* Build outputs for job_updated events.
|
|
* Same structure as job_created.
|
|
*/
|
|
export function buildJobUpdatedOutputs(): Record<string, TriggerOutput> {
|
|
return {
|
|
action: { type: 'string', description: 'The webhook event type (job_updated)' },
|
|
...greenhouseIndexedOutputs,
|
|
payload: { job: buildJobPayload() },
|
|
} as Record<string, TriggerOutput>
|
|
}
|
|
|
|
/**
|
|
* Build outputs for generic webhook (all events).
|
|
*/
|
|
export function buildWebhookOutputs(): Record<string, TriggerOutput> {
|
|
return {
|
|
action: { type: 'string', description: 'The webhook event type' },
|
|
...greenhouseIndexedOutputs,
|
|
payload: { type: 'json', description: 'Full event payload' },
|
|
} as Record<string, TriggerOutput>
|
|
}
|