Skip to content

feat(webhooks): add GitLab webhook routing#6683

Open
nick-rui wants to merge 1 commit into
NangoHQ:masterfrom
nick-rui:feat/gitlab-webhook-routing
Open

feat(webhooks): add GitLab webhook routing#6683
nick-rui wants to merge 1 commit into
NangoHQ:masterfrom
nick-rui:feat/gitlab-webhook-routing

Conversation

@nick-rui

@nick-rui nick-rui commented Jun 29, 2026

Copy link
Copy Markdown

What

Adds a webhook routing script for GitLab, so GitLab project/group webhooks can be routed to a Nango connection like other providers. Today the gitlab and gitlab-pat providers have no webhook_routing_script, so incoming GitLab webhooks are dropped at the routing gate in webhook.manager.ts (if (!provider['webhook_routing_script']) return 204).

Why

GitLab webhook payloads carry project/group/user identity but no Nango connection id and no single account-level id, and one Nango connection can span many GitLab projects. There was therefore no existing way to map a GitLab webhook to a connection.

How

Routing follows the same query-param pattern already used by cal.com and folk:

  • The integrator appends ?nangoConnectionId=<connectionId> to the Nango webhook URL when registering the GitLab project/group hook (e.g. https://api.nango.dev/webhook/<environmentUuid>/gitlab?nangoConnectionId=<connectionId>). The handler reads it from the query string (falling back to a nangoConnectionId body field, mirroring cal.com) and resolves the connection directly via propName: 'connectionId'.
  • Authenticity is verified with GitLab's X-Gitlab-Token header. Unlike most providers GitLab does not sign the payload — it echoes back verbatim the secret token configured on the hook — so verification is a constant-time compare against the integration's webhook secret (configured via webhook_user_defined_secret: true, stored in integration.custom.webhookSecret, same as folk/linear/autotask). If no secret is configured the request is allowed through, mirroring the other query-param-routed providers.
  • The X-Gitlab-Event header (e.g. Issue Hook, Merge Request Hook, Note Hook) is passed as webhookHeaderValue and matched against sync-config webhook subscriptions (same mechanism github-app uses with x-github-event).

This works for both project-level and group-level GitLab webhooks, since both send the same X-Gitlab-Token / X-Gitlab-Event headers and are routed purely by the nangoConnectionId query param.

Changes

  • packages/server/lib/webhook/gitlab-webhook-routing.ts — new handler.
  • packages/server/lib/webhook/index.ts — export the handler.
  • packages/providers/providers.yaml — add webhook_routing_script: gitlabWebhookRouting, webhook_user_defined_secret: true, and webhook_allowed_query_params: [nangoConnectionId] to both gitlab (OAuth2) and gitlab-pat (API key), since GitLab hooks can target a connection regardless of how it authenticates.
  • packages/server/lib/webhook/gitlab-webhook-routing.unit.test.ts — unit tests mirroring the existing autotask handler tests (query/header routing, body fallback, token validation, missing connection id, forwarding).
  • docs/integrations/all/gitlab.mdx — short "API gotchas" note on webhook routing.

Testing

  • npx prettier --check on the touched files — clean.
  • oxlint -c .oxlintrc.json on the touched files — no errors (only the same no-unsafe-* warnings the existing cal-com handler emits on the any-typed body).
  • npx tsx scripts/validation/providers/validate.ts✅ All providers are valid.
  • vitest run on the new unit test — passing.

Notes for reviewers

  • Secret-config convention: I reused integration.custom.webhookSecret + webhook_user_defined_secret: true (as folk/linear/autotask do). Happy to adjust if connection-level secrets are preferred for GitLab.
  • I added the routing to both gitlab and gitlab-pat; let me know if only one is wanted.
  • Event subscription values use GitLab's X-Gitlab-Event header strings verbatim (Issue Hook, Merge Request Hook, Note Hook, etc.).
  • I kept the docs change minimal (a note on the existing catalog page) rather than adding a new guide page + nav entry; happy to expand into a full webhooks guide if that's the preference.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 5 files

Confidence score: 4/5

  • In packages/server/lib/webhook/gitlab-webhook-routing.ts, the fallback access to body.nangoConnectionId can throw when body is null/undefined or not an object, which would return an unhandled runtime failure instead of the expected webhook_missing_connection_id response for bad webhook payloads — add an object/null guard (or optional chaining with type checks) before reading the fallback field before merging.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/server/lib/webhook/gitlab-webhook-routing.ts Outdated
@nick-rui nick-rui force-pushed the feat/gitlab-webhook-routing branch from 3b48de4 to af93008 Compare June 29, 2026 23:27
GitLab project/group webhooks were dropped at the routing gate because
the gitlab and gitlab-pat providers had no webhook_routing_script.

Route GitLab webhooks the same way cal.com and folk do: read a
nangoConnectionId query param from the webhook URL (with a body-field
fallback) and resolve the connection directly via propName 'connectionId'.
Authenticity is verified with GitLab's X-Gitlab-Token shared-secret header
(constant-time compare against the integration's configured webhook secret),
and the X-Gitlab-Event header is used as the event type for subscription
matching.
@nick-rui nick-rui force-pushed the feat/gitlab-webhook-routing branch from af93008 to f45ca88 Compare June 29, 2026 23:41
@nick-rui nick-rui marked this pull request as ready for review June 29, 2026 23:41
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