Documentation Index
Fetch the complete documentation index at: https://ngrok.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
Instead of implementing webhook validation and routing logic separately in every service, a webhook gateway provides a single, secure entry point for all third-party webhooks from providers like Stripe, Twilio, Slack, and GitHub. This centralized approach validates webhook signatures, prevents tampering, and routes authenticated requests to the appropriate internal services in production environments.
With this setup, you can:
- Validate webhook signatures from 70+ supported providers in a single place
- Route authenticated webhooks to appropriate services based on the provider
- Prevent webhook spoofing and tampering with cryptographic verification
- Standardize webhook handling across your entire infrastructure
- Apply consistent logging and monitoring to all webhook traffic
1. Create endpoints for your services
Start internal Agent Endpoints for the services that will handle webhooks from different providers.
You can also use one of the SDKs or the Kubernetes Operator.
For a payment service handling Stripe webhooks:
ngrok http $PAYMENT_SERVICE_PORT --url https://payment-service.internal
For a notification service handling Slack webhooks:
ngrok http $NOTIFICATION_SERVICE_PORT --url https://notification-service.internal
For a deployment service handling GitHub webhooks:
ngrok http $DEPLOYMENT_SERVICE_PORT --url https://deployment-service.internal
2. Reserve a domain
Navigate to the Domains section of the ngrok dashboard and click New + to reserve a free static domain like or a custom domain you already own.
We’ll refer to this domain as $NGROK_DOMAIN from here on out.
3. Create a Cloud Endpoint
Navigate to the Endpoints section of the ngrok dashboard, then click New + and Cloud Endpoint.
In the URL field, enter the domain you just reserved to finish creating your Cloud Endpoint.
4. (Optional) Create a vault and secrets
For production environments, store your webhook secrets securely using Secrets for Traffic Policy. This step is optional—you can also use plaintext secrets directly in your policy.
Create a vault to store your webhook secrets:
ngrok api vaults create --name "webhook-secrets" --description "Webhook validation secrets"
Add your credentials to the vault, replacing the names and values, plus changing $VAULT_ID to match the vault ID from the response:
# Add Stripe webhook secret
ngrok api secrets create \
--name "stripe-secret" \
--value "your_stripe_secret_here" \
--vault-id "vault_2yNPzuk6GjHrx3mlOCkJK42RsdR"
# Add Slack webhook secret
ngrok api secrets create \
--name "slack-secret" \
--value "your_slack_verification_token_here" \
--vault-id "vault_2yNPzuk6GjHrx3mlOCkJK42RsdR"
# Add GitHub webhook secret
ngrok api secrets create \
--name "github-secret" \
--value "your_github_webhook_secret_here" \
--vault-id "vault_2yNPzuk6GjHrx3mlOCkJK42RsdR"
5. Apply Traffic Policy to your Cloud Endpoint
While viewing your new Cloud Endpoint in the dashboard, copy one of the policies below and paste it into the Traffic Policy editor.
on_http_request:
- expressions:
# Route Stripe webhooks to payment service
- "req.url.path.startsWith('/stripe')"
actions:
- type: verify-webhook
config:
provider: stripe
secret: "${secrets.get('webhook-secrets', 'stripe-secret')}"
- type: forward-internal
config:
url: https://payment-service.internal
- expressions:
# Route Slack webhooks to notification service
- "req.url.path.startsWith('/slack')"
actions:
- type: verify-webhook
config:
provider: slack
secret: "${secrets.get('webhook-secrets', 'slack-secret')}"
- type: forward-internal
config:
url: https://notification-service.internal
- expressions:
# Route GitHub webhooks to deployment service
- "req.url.path.startsWith('/github')"
actions:
- type: verify-webhook
config:
provider: github
secret: "${secrets.get('webhook-secrets', 'github-secret')}"
- type: forward-internal
config:
url: https://deployment-service.internal
What’s happening here? This policy routes incoming webhook requests based on their URL path. Each route first validates the webhook signature using secrets stored in your encrypted vault, then forwards authenticated requests to the appropriate internal service. If signature verification fails, the request is automatically rejected, protecting your services from spoofed or tampered webhooks.on_http_request:
- expressions:
# Route Stripe webhooks to payment service
- "req.url.path.startsWith('/stripe')"
actions:
- type: verify-webhook
config:
provider: stripe
secret: "whsec_your_stripe_secret_here"
- type: forward-internal
config:
url: https://payment-service.internal
- expressions:
# Route Slack webhooks to notification service
- "req.url.path.startsWith('/slack')"
actions:
- type: verify-webhook
config:
provider: slack
secret: "your_slack_verification_token_here"
- type: forward-internal
config:
url: https://notification-service.internal
- expressions:
# Route GitHub webhooks to deployment service
- "req.url.path.startsWith('/github')"
actions:
- type: verify-webhook
config:
provider: github
secret: "your_github_webhook_secret_here"
- type: forward-internal
config:
url: https://deployment-service.internal
What’s happening here? This policy routes incoming webhook requests based on their URL path. Each route first validates the webhook signature using plaintext secrets, then forwards authenticated requests to the appropriate internal service. If signature verification fails, the request is automatically rejected, protecting your services from spoofed or tampered webhooks.
6. Try out your endpoint
Visit the domain you reserved either in the browser or in the terminal using a tool like curl.
You should see the app or service at the port connected to your internal Agent Endpoint.
Configure your webhook endpoints in each provider’s dashboard:
- Stripe:
https://$NGROK_DOMAIN/stripe
- Slack:
https://$NGROK_DOMAIN/slack
- GitHub:
https://$NGROK_DOMAIN/github
When webhooks are sent from these providers, they’ll be validated and routed to your internal services automatically.
What’s next?