Skip to content

Commit 30080bc

Browse files
committed
fix(webhooks): scope actor reuse to inline execution only
Addresses review: a queued/Trigger.dev webhook can outlive a workspace billed-account change, so reusing the route-resolved actor there could gate against a stale account. Set resolvedActorUserId only on the in-process inline payload (sub-second after resolution); queued and persisted payloads omit it, so the background pass re-resolves the current billed account. Gates unchanged.
1 parent 0cd01da commit 30080bc

2 files changed

Lines changed: 20 additions & 5 deletions

File tree

apps/sim/background/webhook-execution.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,14 @@ export type WebhookExecutionPayload = {
241241
webhookReceivedAt?: number
242242
/** Epoch ms of the originating provider interaction (e.g. Slack x-slack-request-timestamp). */
243243
triggerTimestampMs?: number
244+
/**
245+
* Billing actor resolved by the webhook route, set ONLY for in-process inline
246+
* execution that runs microseconds after resolution. The background pass reuses
247+
* it to skip the redundant billed-account lookup. Deliberately absent on queued
248+
* (Trigger.dev) and persisted payloads — a deferred run could outlive a
249+
* billed-account change, so it re-resolves the current actor instead.
250+
*/
251+
resolvedActorUserId?: string
244252
}
245253

246254
export async function executeWebhookJob(payload: WebhookExecutionPayload) {
@@ -367,10 +375,11 @@ async function executeWebhookJobInternal(
367375
skipUsageLimits: true,
368376
workspaceId: payload.workspaceId,
369377
loggingSession,
370-
// The webhook route already resolved the billing actor and carried it as
371-
// payload.userId. Reuse it to skip the redundant billed-account lookup; the
372-
// ban and archived-workflow gates still run fresh here.
373-
resolvedActorUserId: payload.userId,
378+
// Reuse the route-resolved actor only for inline execution (set on the
379+
// in-process payload). When absent — queued/Trigger.dev runs — preprocessing
380+
// re-resolves the current billed account. Either way the ban and
381+
// archived-workflow gates run fresh against the resolved actor.
382+
resolvedActorUserId: payload.resolvedActorUserId,
374383
})
375384

376385
if (!preprocessResult.success) {

apps/sim/lib/webhooks/processor.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,10 +667,16 @@ export async function queueWebhookExecution(
667667
`[${options.requestId}] Queued ${foundWebhook.provider} webhook execution ${jobId} via inline backend`
668668
)
669669

670+
// Inline runs in-process microseconds after the route resolved the actor,
671+
// so reuse it to skip a redundant billed-account lookup. Set only on the
672+
// in-process payload — the enqueued/persisted copy omits it so any deferred
673+
// re-run re-resolves the current billed account.
674+
const inlinePayload = { ...payload, resolvedActorUserId: actorUserId }
675+
670676
void (async () => {
671677
try {
672678
await jobQueue.startJob(jobId)
673-
const output = await executeWebhookJob(payload)
679+
const output = await executeWebhookJob(inlinePayload)
674680
await jobQueue.completeJob(jobId, output)
675681
} catch (error) {
676682
const errorMessage = toError(error).message

0 commit comments

Comments
 (0)