Waleed 07c026a917 fix(ashby): repair broken idempotency dedup and clarify duplicate-webhook errors (#5518)
* fix(ashby): repair broken idempotency dedup and clarify duplicate-webhook errors

extractIdempotencyId looked for a webhookActionId field that does not
exist anywhere in Ashby's webhook payload schema (confirmed against the
live OpenAPI spec), so every delivery — including Ashby's own retries —
got a fresh random dedup key and could re-execute workflows. Derive the
key from the affected resource's id plus its updatedAt/decidedAt instead.

Also surface a clear, actionable message when Ashby's webhook.create
rejects a request as a duplicate (seen repeatedly in production logs,
where the outbox retried the same failing subscription for ~23 minutes
before dead-lettering) instead of the generic API error passthrough.

Also add the interview stage `type` field to trigger outputs (present
in Ashby's schema, useful for a stage-change trigger) and fix the
employmentType description to match Ashby's actual enum values.

* fix(ashby): correct offer status enum values in trigger output descriptions

acceptanceStatus listed a non-existent "WaitingOnResponse" value and
offerStatus was missing "WaitingOnApprovalDefinition" — both caught on
a second pass re-checking every enum against Ashby's live OpenAPI spec.

* fix(ashby): harden idempotency key against Greptile-flagged collision risks

- Return null (skip dedup) when application.updatedAt is absent instead
  of collapsing to an empty string, which could collide two distinct
  events sharing an application id onto the same key.
- Drop decidedAt from the offerCreate key. It's populated only after
  the fact, so including it gave a retry of the same delivery a
  different key than the original attempt once the candidate
  responded, defeating dedup. offer.id alone is already stable and
  unique per created offer.

* fix(ashby): fall back to a content fingerprint instead of skipping dedup

Returning null when application.updatedAt was missing avoided false
collisions between distinct events, but also disabled retry dedup
entirely for that payload shape — an Ashby retry would get a random
key from the idempotency service's own fallback and re-run the
workflow.

Extract the fallback-fingerprint helper (sha256 of a stably-serialized
payload) out of salesforce.ts into the shared providers/utils.ts, and
use it in ashby.ts: identical retried bytes hash identically (dedup
still works), while two genuinely different events hash differently
(no false collision).

* chore(ashby): remove inline comments, let names carry the intent

* fix(ashby): fingerprint the full data payload, not just application

Hashing only data.application missed other fields (e.g. offer on
candidateHire) when updatedAt is absent, so two deliveries sharing an
application snapshot but differing elsewhere in data could collide
onto the same idempotency key.

* fix(ashby): rename output field 'type' to 'stageType' to fix build

TriggerOutput reserves the 'type' key for the output's own JSON type
(e.g. 'string'), so a nested field literally named 'type' inside
currentInterviewStage collided with that meta-field and broke the
Record<string, TriggerOutput> cast — passing local type-check (which
turbo was silently serving a stale cached pass for) but failing
Next.js's build-time type check in CI. Renamed to stageType, matching
the existing eventType-style convention used elsewhere for API fields
literally called 'type'.

* fix(ashby): rename currentInterviewStage.type to stageType in delivered data too

Renaming the field in the output schema alone (to fix the TriggerOutput
build collision) left a mismatch: Ashby's real payload still has
currentInterviewStage.type, so a picker/expression using the schema's
declared stageType would resolve to undefined at runtime. formatInput
now renames the field in the actual delivered payload so it matches
what the schema declares.

* fix(ashby): fold offer id into the idempotency key when present

When application.updatedAt is present, the key ignored sibling data
fields — a candidateHire delivery carries both application and offer,
so two deliveries sharing an application snapshot could collide even
though they concern different offers. Append offer.id to the
discriminator when present; it's the only sibling object Ashby's
schema ever pairs with application.
2026-07-08 15:34:18 -07:00

Sim.ai Documentation Discord X

Ask DeepWiki Set Up with Cursor

Sim — Integrate, Context, Build, and Monitor AI agents

A workspace to build, deploy and manage AI agents and workflows.

Quickstart

Cloud-hosted: sim.ai

Open sim.ai

Self-hosted

npx simstudio

Open http://localhost:3000

Docker must be installed and running. Use -p, --port <port> to run Sim on a different port, or --no-pull to skip pulling the latest Docker images.

The Sim platform — chat on the left, the visual workflow builder on the right

Capabilities

  • Connect 1,000+ integrations and every major LLM
  • Add Slack, Notion, HubSpot, Salesforce, databases, and more
  • Build agents visually, conversationally, or with code
  • Ingest files, knowledge bases, and structured table data
  • Monitor runs, logs, schedules, and workflow activity

One workspace, every surface

Chat and workflows are just the start — tables, files, knowledge, and scheduled tasks all live in the same workspace.

Tables in Sim — structured data your agents can query

Tables — a database, built in

Files in Sim — documents for your team and every agent

Files — one store for your team and every agent

Knowledge bases in Sim — synced docs your agents can search

Knowledge — your agents' memory

Scheduled tasks in Sim — recurring agent runs on a calendar

Scheduled tasks — runs on your schedule

Self-hosting

Docker Compose

git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d

Open http://localhost:3000

Sim also supports local models via Ollama and vLLM. See the Docker self-hosting docs for setup details.

Manual Setup

Requirements: Bun, Node.js v20+, PostgreSQL 12+ with pgvector

  1. Clone and install:
git clone https://github.com/simstudioai/sim.git
cd sim
bun install
bun run prepare  # Set up pre-commit hooks
  1. Set up PostgreSQL with pgvector:
docker run --name simstudio-db -e POSTGRES_PASSWORD=your_password -e POSTGRES_DB=simstudio -p 5432:5432 -d pgvector/pgvector:pg17

Or install manually via the pgvector guide.

  1. Configure environment:
cp apps/sim/.env.example apps/sim/.env
# Create your secrets
perl -i -pe "s/your_encryption_key/$(openssl rand -hex 32)/" apps/sim/.env
perl -i -pe "s/your_internal_api_secret/$(openssl rand -hex 32)/" apps/sim/.env
perl -i -pe "s/your_api_encryption_key/$(openssl rand -hex 32)/" apps/sim/.env
# DB configs for migration
cp packages/db/.env.example packages/db/.env
# Edit both .env files to set DATABASE_URL="postgresql://postgres:your_password@localhost:5432/simstudio"
  1. Run migrations:
cd packages/db && bun run db:migrate
  1. Start development servers:
bun run dev:full  # Starts Next.js app and realtime socket server

Or run separately: bun run dev (Next.js) and cd apps/sim && bun run dev:sockets (realtime).

Chat API Keys

Chat is a Sim-managed service. To use Chat on a self-hosted instance:

  • Go to https://sim.ai → Settings → Chat keys and generate a Chat API key
  • Set COPILOT_API_KEY environment variable in your self-hosted apps/sim/.env file to that value

Environment Variables

See the environment variables reference for the full list, or apps/sim/.env.example for defaults.

Tech Stack

Next.js · Bun · PostgreSQL · Drizzle · Better Auth · Tailwind — and the rest of the stack

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

Built by the Sim team in San Francisco

Languages
TypeScript 77%
MDX 20.8%
JavaScript 1.9%
CSS 0.1%