@@ -493,6 +493,7 @@ async function executeWebhookJobInternal(
493493 )
494494 loggingSession . setExecutionDeadlineAt ( getExecutionDeadlineAt ( timeoutController . signal ) )
495495
496+ const preprocessStartedAt = Date . now ( )
496497 const preprocessResult = await preprocessExecution ( {
497498 workflowId : payload . workflowId ,
498499 userId : payload . userId ,
@@ -509,6 +510,7 @@ async function executeWebhookJobInternal(
509510 executionType : 'async' ,
510511 executionDeadlineAt : getExecutionDeadlineAt ( timeoutController . signal ) ?. getTime ( ) ,
511512 } )
513+ const preprocessEndedAt = Date . now ( )
512514
513515 if ( ! preprocessResult . success ) {
514516 throw new Error ( preprocessResult . error ?. message || 'Preprocessing failed in background job' )
@@ -564,6 +566,7 @@ async function executeWebhookJobInternal(
564566 ? resolveCredentialAccountUserId ( payload . credentialId )
565567 : Promise . resolve ( undefined ) ,
566568 ] )
569+ const loadsEndedAt = Date . now ( )
567570 const credentialAccountUserId = resolvedCredentialUserId
568571 if ( payload . credentialId && ! credentialAccountUserId ) {
569572 logger . warn (
@@ -626,6 +629,7 @@ async function executeWebhookJobInternal(
626629 } ,
627630 }
628631 )
632+ const providerConfigEndedAt = Date . now ( )
629633
630634 if ( handler . formatInput ) {
631635 const result = await handler . formatInput ( {
@@ -642,6 +646,7 @@ async function executeWebhookJobInternal(
642646 } else {
643647 input = payload . body as Record < string , unknown > | null
644648 }
649+ const formatInputEndedAt = Date . now ( )
645650
646651 if ( ! input && handler . handleEmptyInput ) {
647652 const skipResult = handler . handleEmptyInput ( requestId )
@@ -791,6 +796,10 @@ async function executeWebhookJobInternal(
791796 payload . webhookReceivedAt !== undefined ? now - payload . webhookReceivedAt : undefined ,
792797 triggerAgeMs :
793798 payload . triggerTimestampMs !== undefined ? now - payload . triggerTimestampMs : undefined ,
799+ preprocessMs : preprocessEndedAt - preprocessStartedAt ,
800+ loadsMs : loadsEndedAt - preprocessEndedAt ,
801+ providerConfigMs : providerConfigEndedAt - loadsEndedAt ,
802+ formatInputMs : formatInputEndedAt - providerConfigEndedAt ,
794803 } )
795804 }
796805
@@ -802,9 +811,33 @@ async function executeWebhookJobInternal(
802811 [ ]
803812 )
804813
814+ /**
815+ * The dispatch-latency line above fires before executeWorkflowCore, so it cannot
816+ * see the core's own setup (custom-block gate, env resolution, logging start,
817+ * serialization). This logs once, when the first block actually starts — the
818+ * moment that decides a trigger_id-bound provider's race against its expiry.
819+ */
820+ let executorStartLogged = false
821+ const logExecutorStart = async ( ) => {
822+ if ( executorStartLogged ) return
823+ executorStartLogged = true
824+ if ( payload . webhookReceivedAt === undefined && payload . triggerTimestampMs === undefined ) {
825+ return
826+ }
827+ const now = Date . now ( )
828+ logger . info ( `[${ requestId } ] Webhook executor started` , {
829+ workflowId : payload . workflowId ,
830+ provider : payload . provider ,
831+ executorStartLatencyMs :
832+ payload . webhookReceivedAt !== undefined ? now - payload . webhookReceivedAt : undefined ,
833+ executorStartTriggerAgeMs :
834+ payload . triggerTimestampMs !== undefined ? now - payload . triggerTimestampMs : undefined ,
835+ } )
836+ }
837+
805838 const executionResult = await executeWorkflowCore ( {
806839 snapshot,
807- callbacks : { } ,
840+ callbacks : { onBlockStart : logExecutorStart } ,
808841 loggingSession,
809842 trustedInitialResolvedSecretTraceProvenance :
810843 resolvedSecretTraceRegistry . exportProvenanceForValue ( triggerInput ) ,
0 commit comments