chore: import upstream snapshot with attribution
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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,41 @@
import { GreenhouseIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildCandidateHiredOutputs,
buildGreenhouseExtraFields,
greenhouseSetupInstructions,
greenhouseTriggerOptions,
} from '@/triggers/greenhouse/utils'
import type { TriggerConfig } from '@/triggers/types'
/**
* Greenhouse Candidate Hired Trigger
*
* This is the PRIMARY trigger - it includes the dropdown for selecting trigger type.
* Fires when a candidate is marked as hired in Greenhouse.
*/
export const greenhouseCandidateHiredTrigger: TriggerConfig = {
id: 'greenhouse_candidate_hired',
name: 'Greenhouse Candidate Hired',
provider: 'greenhouse',
description: 'Trigger workflow when a candidate is hired',
version: '1.0.0',
icon: GreenhouseIcon,
subBlocks: buildTriggerSubBlocks({
triggerId: 'greenhouse_candidate_hired',
triggerOptions: greenhouseTriggerOptions,
includeDropdown: true,
setupInstructions: greenhouseSetupInstructions('Candidate Hired'),
extraFields: buildGreenhouseExtraFields('greenhouse_candidate_hired'),
}),
outputs: buildCandidateHiredOutputs(),
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
}
@@ -0,0 +1,39 @@
import { GreenhouseIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildCandidateRejectedOutputs,
buildGreenhouseExtraFields,
greenhouseSetupInstructions,
greenhouseTriggerOptions,
} from '@/triggers/greenhouse/utils'
import type { TriggerConfig } from '@/triggers/types'
/**
* Greenhouse Candidate Rejected Trigger
*
* Fires when a candidate is rejected from a position.
*/
export const greenhouseCandidateRejectedTrigger: TriggerConfig = {
id: 'greenhouse_candidate_rejected',
name: 'Greenhouse Candidate Rejected',
provider: 'greenhouse',
description: 'Trigger workflow when a candidate is rejected',
version: '1.0.0',
icon: GreenhouseIcon,
subBlocks: buildTriggerSubBlocks({
triggerId: 'greenhouse_candidate_rejected',
triggerOptions: greenhouseTriggerOptions,
setupInstructions: greenhouseSetupInstructions('Candidate Rejected'),
extraFields: buildGreenhouseExtraFields('greenhouse_candidate_rejected'),
}),
outputs: buildCandidateRejectedOutputs(),
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
}
@@ -0,0 +1,39 @@
import { GreenhouseIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildCandidateStageChangeOutputs,
buildGreenhouseExtraFields,
greenhouseSetupInstructions,
greenhouseTriggerOptions,
} from '@/triggers/greenhouse/utils'
import type { TriggerConfig } from '@/triggers/types'
/**
* Greenhouse Candidate Stage Change Trigger
*
* Fires when a candidate moves to a different interview stage.
*/
export const greenhouseCandidateStageChangeTrigger: TriggerConfig = {
id: 'greenhouse_candidate_stage_change',
name: 'Greenhouse Candidate Stage Change',
provider: 'greenhouse',
description: 'Trigger workflow when a candidate changes interview stages',
version: '1.0.0',
icon: GreenhouseIcon,
subBlocks: buildTriggerSubBlocks({
triggerId: 'greenhouse_candidate_stage_change',
triggerOptions: greenhouseTriggerOptions,
setupInstructions: greenhouseSetupInstructions('Candidate Stage Change'),
extraFields: buildGreenhouseExtraFields('greenhouse_candidate_stage_change'),
}),
outputs: buildCandidateStageChangeOutputs(),
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
}
+8
View File
@@ -0,0 +1,8 @@
export { greenhouseCandidateHiredTrigger } from './candidate_hired'
export { greenhouseCandidateRejectedTrigger } from './candidate_rejected'
export { greenhouseCandidateStageChangeTrigger } from './candidate_stage_change'
export { greenhouseJobCreatedTrigger } from './job_created'
export { greenhouseJobUpdatedTrigger } from './job_updated'
export { greenhouseNewApplicationTrigger } from './new_application'
export { greenhouseOfferCreatedTrigger } from './offer_created'
export { greenhouseWebhookTrigger } from './webhook'
@@ -0,0 +1,39 @@
import { GreenhouseIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildGreenhouseExtraFields,
buildJobCreatedOutputs,
greenhouseSetupInstructions,
greenhouseTriggerOptions,
} from '@/triggers/greenhouse/utils'
import type { TriggerConfig } from '@/triggers/types'
/**
* Greenhouse Job Created Trigger
*
* Fires when a new job posting is created.
*/
export const greenhouseJobCreatedTrigger: TriggerConfig = {
id: 'greenhouse_job_created',
name: 'Greenhouse Job Created',
provider: 'greenhouse',
description: 'Trigger workflow when a new job is created',
version: '1.0.0',
icon: GreenhouseIcon,
subBlocks: buildTriggerSubBlocks({
triggerId: 'greenhouse_job_created',
triggerOptions: greenhouseTriggerOptions,
setupInstructions: greenhouseSetupInstructions('Job Created'),
extraFields: buildGreenhouseExtraFields('greenhouse_job_created'),
}),
outputs: buildJobCreatedOutputs(),
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
}
@@ -0,0 +1,39 @@
import { GreenhouseIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildGreenhouseExtraFields,
buildJobUpdatedOutputs,
greenhouseSetupInstructions,
greenhouseTriggerOptions,
} from '@/triggers/greenhouse/utils'
import type { TriggerConfig } from '@/triggers/types'
/**
* Greenhouse Job Updated Trigger
*
* Fires when a job posting is updated.
*/
export const greenhouseJobUpdatedTrigger: TriggerConfig = {
id: 'greenhouse_job_updated',
name: 'Greenhouse Job Updated',
provider: 'greenhouse',
description: 'Trigger workflow when a job is updated',
version: '1.0.0',
icon: GreenhouseIcon,
subBlocks: buildTriggerSubBlocks({
triggerId: 'greenhouse_job_updated',
triggerOptions: greenhouseTriggerOptions,
setupInstructions: greenhouseSetupInstructions('Job Updated'),
extraFields: buildGreenhouseExtraFields('greenhouse_job_updated'),
}),
outputs: buildJobUpdatedOutputs(),
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
}
@@ -0,0 +1,39 @@
import { GreenhouseIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildGreenhouseExtraFields,
buildNewApplicationOutputs,
greenhouseSetupInstructions,
greenhouseTriggerOptions,
} from '@/triggers/greenhouse/utils'
import type { TriggerConfig } from '@/triggers/types'
/**
* Greenhouse New Application Trigger
*
* Fires when a new candidate application is submitted.
*/
export const greenhouseNewApplicationTrigger: TriggerConfig = {
id: 'greenhouse_new_application',
name: 'Greenhouse New Application',
provider: 'greenhouse',
description: 'Trigger workflow when a new application is submitted',
version: '1.0.0',
icon: GreenhouseIcon,
subBlocks: buildTriggerSubBlocks({
triggerId: 'greenhouse_new_application',
triggerOptions: greenhouseTriggerOptions,
setupInstructions: greenhouseSetupInstructions('New Candidate Application'),
extraFields: buildGreenhouseExtraFields('greenhouse_new_application'),
}),
outputs: buildNewApplicationOutputs(),
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
}
@@ -0,0 +1,39 @@
import { GreenhouseIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildGreenhouseExtraFields,
buildOfferCreatedOutputs,
greenhouseSetupInstructions,
greenhouseTriggerOptions,
} from '@/triggers/greenhouse/utils'
import type { TriggerConfig } from '@/triggers/types'
/**
* Greenhouse Offer Created Trigger
*
* Fires when a new offer is created for a candidate.
*/
export const greenhouseOfferCreatedTrigger: TriggerConfig = {
id: 'greenhouse_offer_created',
name: 'Greenhouse Offer Created',
provider: 'greenhouse',
description: 'Trigger workflow when a new offer is created',
version: '1.0.0',
icon: GreenhouseIcon,
subBlocks: buildTriggerSubBlocks({
triggerId: 'greenhouse_offer_created',
triggerOptions: greenhouseTriggerOptions,
setupInstructions: greenhouseSetupInstructions('Offer Created'),
extraFields: buildGreenhouseExtraFields('greenhouse_offer_created'),
}),
outputs: buildOfferCreatedOutputs(),
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
}
@@ -0,0 +1,88 @@
/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import { greenhouseHandler } from '@/lib/webhooks/providers/greenhouse'
import { isGreenhouseEventMatch } from '@/triggers/greenhouse/utils'
describe('isGreenhouseEventMatch', () => {
it('matches mapped trigger ids to Greenhouse action strings', () => {
expect(isGreenhouseEventMatch('greenhouse_new_application', 'new_candidate_application')).toBe(
true
)
expect(isGreenhouseEventMatch('greenhouse_new_application', 'hire_candidate')).toBe(false)
})
it('rejects unknown trigger ids (no permissive fallback)', () => {
expect(isGreenhouseEventMatch('greenhouse_unknown', 'new_candidate_application')).toBe(false)
})
it('builds fallback idempotency keys for nested offer payloads', () => {
const key = greenhouseHandler.extractIdempotencyId!({
action: 'offer_deleted',
payload: {
offer: {
id: 42,
version: 3,
},
},
})
expect(key).toBe('greenhouse:offer_deleted:offer:42:3')
})
})
describe('greenhouseHandler.formatInput', () => {
it('exposes application, candidate, and job ids alongside action and payload', async () => {
const { input } = await greenhouseHandler.formatInput!({
webhook: {},
workflow: { id: 'w', userId: 'u' },
body: {
action: 'new_candidate_application',
payload: {
application: {
id: 100,
candidate: { id: 200 },
jobs: [{ id: 300 }],
},
},
},
headers: {},
requestId: 't',
})
expect(input).toMatchObject({
action: 'new_candidate_application',
applicationId: 100,
candidateId: 200,
jobId: null,
})
expect(input).toHaveProperty('payload')
})
it('reads job id from payload.job and offer job_id', async () => {
const jobFromNested = await greenhouseHandler.formatInput!({
webhook: {},
workflow: { id: 'w', userId: 'u' },
body: {
action: 'job_created',
payload: { job: { id: 55 } },
},
headers: {},
requestId: 't',
})
expect((jobFromNested.input as Record<string, unknown>).jobId).toBe(55)
const jobFromOffer = await greenhouseHandler.formatInput!({
webhook: {},
workflow: { id: 'w', userId: 'u' },
body: {
action: 'offer_created',
payload: { id: 1, application_id: 2, job_id: 66 },
},
headers: {},
requestId: 't',
})
expect((jobFromOffer.input as Record<string, unknown>).jobId).toBe(66)
expect((jobFromOffer.input as Record<string, unknown>).applicationId).toBe(2)
})
})
+357
View File
@@ -0,0 +1,357 @@
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 &gt; Dev Center &gt; 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>
}
+39
View File
@@ -0,0 +1,39 @@
import { GreenhouseIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildGreenhouseExtraFields,
buildWebhookOutputs,
greenhouseSetupInstructions,
greenhouseTriggerOptions,
} from '@/triggers/greenhouse/utils'
import type { TriggerConfig } from '@/triggers/types'
/**
* Greenhouse generic webhook trigger.
* Event filtering is determined by which events you enable on the Greenhouse webhook endpoint.
*/
export const greenhouseWebhookTrigger: TriggerConfig = {
id: 'greenhouse_webhook',
name: 'Greenhouse Webhook (Endpoint Events)',
provider: 'greenhouse',
description:
'Trigger on whichever event types you select for this URL in Greenhouse. Sim does not filter deliveries for this trigger.',
version: '1.0.0',
icon: GreenhouseIcon,
subBlocks: buildTriggerSubBlocks({
triggerId: 'greenhouse_webhook',
triggerOptions: greenhouseTriggerOptions,
setupInstructions: greenhouseSetupInstructions('Greenhouse event types for this URL'),
extraFields: buildGreenhouseExtraFields('greenhouse_webhook'),
}),
outputs: buildWebhookOutputs(),
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
}