Skip to content

Slack interactivity (block_actions) webhooks not forwarded to customer endpoints #5434

@tiffanybalc

Description

@tiffanybalc

Summary
Slack interactivity payloads (block_actions from button clicks, including AI App feedback_buttons) sent to the Nango webhook endpoint at POST /webhook/:environmentUuid/slack are never forwarded to the customer’s configured webhook URL. Events API webhooks (JSON, application/json) forwarding works correctly through the same endpoint and integration.

Reproduction
Configure a Slack integration with forward_webhooks enabled (Events API forwarding already works)
Set the Slack app’s Interactivity Request URL to https://api.nango.dev/webhook//slack (same URL as the Events API Request URL)
Trigger a block_actions event (e.g., click a button in a Slack message posted by the app)
Expected: The parsed payload is forwarded to the customer’s webhook URL as a NangoForwardWebhookBody
Actual: Nothing is forwarded. No errors visible in customer-side logs. Slack receives a response (no timeout), but the payload silently disappears.
Verification
Pointing the Interactivity Request URL directly at our backend (bypassing Nango) confirms Slack is sending valid block_actions payloads. The issue is isolated to Nango’s handling/forwarding.

Likely Root Cause
packages/server/lib/webhook/slack-webhook-routing.ts, line 9 — strict content-type equality check:

if (headers['content-type'] === 'application/x-www-form-urlencoded') {
    try {
        payload = JSON.parse(body['payload'] || body);
    } catch {
        payload = body;
    }
} else {
    payload = body;  // ← Falls through here if content-type has extra params
}

If Slack sends Content-Type: application/x-www-form-urlencoded; charset=utf-8 (with charset parameter), the strict === check fails. The else branch sets payload = body, where body is the Express-parsed form object { payload: '' } (thanks to express.urlencoded() on routes.public.ts:102). Then line 24:

const teamId = payload['team_id'] || payload['team']['id'];
payload['team'] is undefined → TypeError: Cannot read properties of undefined (reading ‘id’). This is caught by webhook.manager.ts:82-84, which returns Err(...) → statusCode: 500 → forwarding is skipped (forwarding requires statusCode === 200 at line 106).

Suggested Fix
Replace the strict equality with a prefix match:

// Before (line 9):
if (headers['content-type'] === 'application/x-www-form-urlencoded') {

// After:
if (headers['content-type']?.startsWith('application/x-www-form-urlencoded')) {
Alternatively, split on ; and trim:

const contentType = headers['content-type']?.split(';')[0]?.trim();
if (contentType === 'application/x-www-form-urlencoded') {

Secondary Issue: No rawBody for form-encoded requests
routes.public.ts only sets req.rawBody in the express.json() verify callback (line 90-92). express.urlencoded() (line 102) has no verify callback:

publicAPI.use(express.json({
    limit: bodyLimit,
    verify: (req: Request, _, buf) => { req.rawBody = buf.toString(); }  // ← rawBody set here
}));
// ...
publicAPI.use(express.urlencoded({ extended: true, limit: bodyLimit }));  // ← No verify, no rawBody

For Slack interactivity requests, req.rawBody is undefined. This isn’t currently causing a crash (the Slack handler doesn’t use rawBody), but it means rawBody passed to routeWebhook() is undefined, which could affect any future use.

Environment
Nango version: 0.69.31
Slack integration with OAuth2, team.id in token_response_metadata
forward_webhooks: true, webhook URL configured
Events API forwarding works correctly for JSON payloads

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions