tnl.dev :: docs

receive webhooks.

Register a stable public URL with a webhook provider and verify its requests.

Give your local app one saved HTTPS webhook public URL. Register it with a provider once, then start the app normally in api/, api-perf/, or both without editing that provider's endpoint again.

set up an endpoint

Declare a named local service, the exact path handled by your app, and a provider:

tnl.yml
version: 1
tnl:
  services:
    api:
      public_url: {}
  webhooks:
    payments:
      service: api
      path: /api/webhooks/stripe
      provider: stripe

Configure your framework integration to register service api, then start the app with its normal command:

tnl check
tnl auth login
pnpm dev

Read the full webhook public URL, including its path, from tnl.webhooks?.payments?.url in the integrated app after preparation. This is the webhook integration URL, not the worktree app URL. For an already-running handler without an SDK, declare an explicit address target alongside public_url: {} and run tnl publish api; tnl does not start that app. Append the declared endpoint path to the webhook origin printed by that publisher to get the full provider URL.

Register the full URL with Stripe. Your app must handle POST /api/webhooks/stripe, verify Stripe's signature over the raw body, and return 2xx. The Stripe walkthrough covers the Dashboard settings, signing secret, and a test event. The webhook hostname belongs to the project; ordinary app preview URLs belong to individual worktrees. A stopped local app cannot receive requests.

choose a provider

Each guide gives you a configuration to start with, the provider's registration steps, how your app checks a request, and a way to send or inspect a test delivery.

what tnl checks

Ingress applies the union of the configured source IP ranges to the webhook hostname: it cannot see encrypted HTTP paths. Once visitor TLS reaches local tnl, it checks the exact path, method, and source IP for each endpoint before reading the body. The selected tnl server supplies each provider's source-IP policy. When a provider has no dependable sender list, that policy is "*"; any IP can reach the exact endpoint. An optional source_ips list replaces the server policy. A missing policy never turns into unrestricted access. Your app still needs to verify the provider's signature, token, or other authentication.

By default, most providers use fanout: all ready worktrees receive the same event and must return 2xx before tnl replies 200 to a POST. If one fails, a provider retry can reach all of them again. Discord, Slack, Twilio, Telegram, and Amazon SNS use selected by default because their verification or delivery may need one particular response. In that worktree, run tnl webhook use NAME before registering the URL. The selected handler's response goes to the provider; without a ready selection, tnl returns 503. See webhook configuration for methods, overrides, and limits.