(
request: NextRequest,
context: { params: Promise<{ path: string }> }
)
| 66 | ) |
| 67 | |
| 68 | async function handleWebhookPost( |
| 69 | request: NextRequest, |
| 70 | context: { params: Promise<{ path: string }> } |
| 71 | ): Promise<NextResponse> { |
| 72 | const receivedAt = Date.now() |
| 73 | /** |
| 74 | * Slack signs every interactive request with the originating interaction time. |
| 75 | * Capturing it lets the executor surface the true trigger_id age (the window |
| 76 | * that expires at 3s) instead of only the in-workflow block timings. |
| 77 | */ |
| 78 | const slackRequestTimestamp = request.headers.get('x-slack-request-timestamp') |
| 79 | const triggerTimestampMs = slackRequestTimestamp |
| 80 | ? Number(slackRequestTimestamp) * 1000 |
| 81 | : undefined |
| 82 | |
| 83 | const requestId = generateRequestId() |
| 84 | const parsed = await parseRequest(webhookTriggerPostContract, request, context) |
| 85 | if (!parsed.success) return parsed.response |
| 86 | const { path } = parsed.data.params |
| 87 | |
| 88 | const earlyChallenge = await handleProviderChallenges({}, request, requestId, path) |
| 89 | if (earlyChallenge) { |
| 90 | return earlyChallenge |
| 91 | } |
| 92 | |
| 93 | const parseResult = await parseWebhookBody(request, requestId) |
| 94 | |
| 95 | // Check if parseWebhookBody returned an error response |
| 96 | if (parseResult instanceof NextResponse) { |
| 97 | return parseResult |
| 98 | } |
| 99 | |
| 100 | const { body, rawBody } = parseResult |
| 101 | |
| 102 | const challengeResponse = await handleProviderChallenges(body, request, requestId, path, rawBody) |
| 103 | if (challengeResponse) { |
| 104 | return challengeResponse |
| 105 | } |
| 106 | |
| 107 | // Find all webhooks for this path (supports credential set fan-out where multiple webhooks share a path) |
| 108 | const allWebhooksForPath = await findAllWebhooksForPath({ requestId, path }) |
| 109 | |
| 110 | // Internal trigger providers (sim, table) are fired in-process, never over |
| 111 | // HTTP. Their rows still register a path, so reject deliveries here to keep |
| 112 | // forged events out. |
| 113 | const webhooksForPath = allWebhooksForPath.filter( |
| 114 | ({ webhook: foundWebhook }) => !isInternalTriggerProvider(foundWebhook.provider) |
| 115 | ) |
| 116 | |
| 117 | if (allWebhooksForPath.length > 0 && webhooksForPath.length === 0) { |
| 118 | logger.warn(`[${requestId}] Rejected HTTP delivery to internal trigger path: ${path}`) |
| 119 | return new NextResponse('Not Found', { status: 404 }) |
| 120 | } |
| 121 | |
| 122 | if (webhooksForPath.length === 0) { |
| 123 | const verificationResponse = await handlePreLookupWebhookVerification( |
| 124 | request.method, |
| 125 | body as Record<string, unknown> | undefined, |
no test coverage detected