Skip to content

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

Merged
waleedlatif1 merged 9 commits into
stagingfrom
validate-ashby-trigger
Jul 8, 2026
Merged

fix(ashby): repair broken idempotency dedup and clarify duplicate-webhook errors#5518
waleedlatif1 merged 9 commits into
stagingfrom
validate-ashby-trigger

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Ran a full /validate-trigger audit on the Ashby webhook trigger against Ashby's live API docs, and checked production logs for real-world failures
  • extractIdempotencyId was reading a webhookActionId field that doesn't exist anywhere in Ashby's webhook payload schema (verified against the live OpenAPI spec) — every delivery, including Ashby's own retries, got a random dedup key that could re-execute workflows. Now derives a stable key from the affected resource's id + updatedAt/decidedAt
  • Production logs showed createSubscription hammering Ashby for ~23 minutes on a "Duplicate webhook" error before dead-lettering, with only the raw Ashby error surfaced. Added a specific, actionable message (Ashby has no webhook.list endpoint, so this can't self-heal — the user has to clean up the stale webhook in Ashby)
  • Added the interview stage type field to trigger outputs (present in Ashby's schema, useful for the stage-change trigger) and fixed the employmentType description to match Ashby's real enum values
  • Confirmed registration/deregistration is fully automatic on deploy/undeploy through the shared provider-agnostic lifecycle (no Ashby-specific code in orchestration files)

Type of Change

  • Bug fix

Testing

  • Added ashby.test.ts covering auth, matchEvent, formatInput, and the fixed idempotency logic (13 tests, all passing)
  • bun run type-check, bun run lint, bun run check:api-validation all pass clean

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…hook 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.
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 8, 2026 10:28pm

Request Review

@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Idempotency key format changes affect whether Ashby webhook deliveries dedupe or re-run workflows; incorrect logic could still duplicate or suppress runs, though the fix targets a prior broken dedup path and is heavily tested.

Overview
Ashby webhook deduplication no longer keys on the nonexistent webhookActionId field. extractIdempotencyId now builds stable keys from action plus resource ids (application, offer, candidate, job), using updatedAt when present, an optional offer suffix for hire-style payloads, and a shared buildFallbackDeliveryFingerprint when timestamps are missing.

Trigger input shaping maps currentInterviewStage.type to stageType in formatInput, with matching stageType fields added to Ashby trigger output schemas; offer/job enum descriptions are aligned with Ashby’s docs.

Subscription UX surfaces a specific message when Ashby returns a duplicate-webhook error during createSubscription.

Refactor: stableSerialize / buildFallbackDeliveryFingerprint move from Salesforce’s provider file into providers/utils.ts (Salesforce imports the shared helper). New ashby.test.ts covers auth, event matching, formatting, and idempotency behavior.

Reviewed by Cursor Bugbot for commit b866793. Configure here.

…ptions

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.
@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes Ashby webhook handling and updates the related trigger outputs. The main changes are:

  • Stable Ashby idempotency keys from resource data instead of a missing webhook field.
  • Shared fallback delivery fingerprinting for provider dedup keys.
  • Clearer duplicate-webhook subscription errors.
  • Ashby trigger output updates for interview stage and enum fields.
  • New Ashby provider tests for auth, matching, formatting, and dedup behavior.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
apps/sim/lib/webhooks/providers/ashby.ts Updates Ashby idempotency keys, stage formatting, and duplicate-webhook messaging.
apps/sim/lib/webhooks/providers/utils.ts Adds a shared deterministic fallback fingerprint helper for provider delivery payloads.
apps/sim/lib/webhooks/providers/salesforce.ts Uses the shared fallback fingerprint helper without changing the Salesforce key shape.
apps/sim/triggers/ashby/utils.ts Adds the Ashby stage type output and updates Ashby enum descriptions.
apps/sim/lib/webhooks/providers/ashby.test.ts Adds provider tests covering Ashby auth, event matching, formatting, and dedup keys.

Reviews (8): Last reviewed commit: "fix(ashby): fold offer id into the idemp..." | Re-trigger Greptile

Comment thread apps/sim/lib/webhooks/providers/ashby.ts Outdated
Comment thread apps/sim/lib/webhooks/providers/ashby.ts Outdated
… 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.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/webhooks/providers/ashby.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 9b2ac1f. Configure here.

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).
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit eab3861. Configure here.

Comment thread apps/sim/lib/webhooks/providers/ashby.ts Outdated
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit ab0dcfa. Configure here.

Comment thread apps/sim/lib/webhooks/providers/ashby.ts Outdated
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.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 2a1862a. Configure here.

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'.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/triggers/ashby/utils.ts
…ed 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.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/webhooks/providers/ashby.ts Outdated
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.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit b866793. Configure here.

@waleedlatif1 waleedlatif1 merged commit 07c026a into staging Jul 8, 2026
13 checks passed
@waleedlatif1 waleedlatif1 deleted the validate-ashby-trigger branch July 8, 2026 22:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant