A webhook authentication, authorization, and validation proxy for enterprise environments.
- Motivation
- Solution
- Security Considerations
- AI-Assisted Configuration
- Alternatives Considered
- Planned Work
- Installation
- Usage
- Development
- Configuring TLS
- Contributing
Development and staging environments are often isolated on private networks or behind VPNs, intentionally unreachable from the public internet. Production applications that handle sensitive internal workflows may be similarly restricted. This is good security practice.
However, modern SaaS applications like Slack, Google Workspace, and GitHub rely on webhooks to notify your systems of events. These webhooks originate from the vendor's infrastructure and must somehow reach your private services. Production applications exposed to the public internet rarely face this problem, but private environments do.
Some vendors provide their own solutions. Stripe CLI can forward webhooks to localhost during development. BrowserStack provides a binary that tunnels browser traffic into private networks. These tools work, but only for their specific vendor.
The traditional general-purpose approach is to open firewall ports for each webhook source. This becomes problematic because:
-
Major cloud providers like AWS and Google use thousands of IP addresses that change frequently. Slack webhooks, for example, can originate from any EC2 instance in AWS.
-
Even if you maintain an up-to-date allowlist, you are trusting that any traffic from those IPs is legitimate. A compromised service sharing that IP space could send malicious requests to your webhook endpoints.
-
Managing firewall rules across multiple teams, each with their own SaaS integrations, does not scale. Webhook sprawl becomes a security and operational burden.
Gatekeeper is a webhook authentication, authorization, and validation proxy. It is the only component exposed to the public internet. Your internal services never need direct internet exposure.
For each incoming webhook request, gatekeeper:
- Checks the source IP against a configured allowlist
- Validates the webhook signature using the provider's published algorithm
- Optionally validates the payload structure against a schema
- Forwards authenticated requests to internal backends
Gatekeeper supports two delivery modes depending on your network architecture.
Direct Forwarding requires an inbound firewall rule from gatekeeperd to your internal service:
Firewall
+--------+ HTTPS +-------------+ : +-----------------+
| Slack |---------->| gatekeeperd |--(*->)->| Internal API |
| GitHub | | | : | 10.1.2.3:8080 |
+--------+ | verify | : +-----------------+
| + forward | :
+-------------+ :
----> = direction of connection initiation
(*->) = direction of firewall hole (ingress)
Relay Mode requires no inbound firewall rules. A relay client inside your private network connects outbound to gatekeeperd and polls for verified webhooks:
Firewall
+--------+ HTTPS +-------------+ : +------------------+
| Slack |---------->| gatekeeperd | : | gatekeeper-relay |
| GitHub | | |<-(<-*)--| |
+--------+ | verify | : | poll + forward |
| + queue | : +--------+---------+
+-------------+ : |
: v
: +-----------------+
No inbound firewall rule needed. : | Internal API |
Only outbound HTTPS from relay client. : | localhost:8080 |
: +-----------------+
----> = direction of connection initiation
(<-*) = direction of firewall hole (egress)
In direct mode, configure a destination URL on your route. Gatekeeperd forwards verified webhooks immediately to that URL. This requires network reachability from gatekeeperd to the destination, typically via a firewall rule.
routes:
- hostname: webhooks.example.com
path: /slack
verifier: my-slack-verifier
destination: http://10.1.2.3:8080/webhooks/slackIn relay mode, configure a relay_token instead of a destination. Gatekeeperd queues verified webhooks. A relay client (gatekeeper-relay) running inside your private network connects outbound to gatekeeperd and long-polls for webhooks matching its token. When a webhook arrives, the relay client retrieves it and forwards it to a local destination.
This architecture means your private network only needs outbound HTTPS access to gatekeeperd. No inbound firewall rules are required.
Server configuration:
routes:
- hostname: webhooks.example.com
path: /slack
verifier: my-slack-verifier
relay_token: "${RELAY_TOKEN_SLACK}"Relay client configuration:
server: "https://gatekeeperd.example.com"
channels:
- name: slack
token: "${RELAY_TOKEN_SLACK}"
destination: "http://localhost:8080/webhooks/slack"Different providers use different signature schemes. Slack uses HMAC-SHA256 with a timestamp-prefixed payload. GitHub uses HMAC-SHA256 directly on the body. Google Calendar uses a simple token header. Gatekeeper implements each scheme correctly, including replay attack protection where the provider supports it.
Supported verifier types:
| Type | Provider | Algorithm |
|---|---|---|
slack |
Slack | HMAC-SHA256 of "v0:{timestamp}:{body}" with replay protection |
github |
GitHub | HMAC-SHA256 of body, hex encoded |
shopify |
Shopify | HMAC-SHA256 of body, base64 encoded |
hmac |
Generic | Configurable HMAC (SHA256/SHA512, hex/base64) |
api_key |
Google Calendar, etc. | Header token comparison |
noop |
Testing | Always succeeds (testing only) |
Gatekeeper supports optional JSON Schema validation of webhook payloads. This is independent of signature verification and provides an additional layer of defense against malformed or malicious payloads.
Key distinction:
- Verifiers authenticate requests (e.g., HMAC signature verification proves the request came from the claimed provider)
- Validators check payload structure (e.g., JSON Schema validation ensures the payload contains required fields with correct types)
A request must pass both verifier and validator checks (if configured) before being forwarded. Validation runs after signature verification succeeds.
Example configuration:
validators:
slack-event:
type: json_schema
schema_file: "schemas/slack/event_callback.json"
routes:
- hostname: webhooks.example.com
path: /slack
verifier: my-slack-verifier # First: verify signature
validator: slack-event # Then: validate payload structure
destination: http://backend:8080/webhooks/slackSchema validation is off by default. See docs/USAGE.md for detailed configuration options.
All routing, verification, and allowlist configuration lives in a single YAML file. Adding a new webhook endpoint means adding a few lines of configuration. No code changes required.
routes:
- hostname: slack-webhooks.example.com
path: /events
ip_allowlist: aws
verifier: my-slack-verifier
destination: http://internal-service:8080/webhooks/slackEnterprise environments often have multiple teams, each with their own SaaS accounts and signing secrets. Gatekeeper supports this through its configuration structure. Each route specifies its own verifier, and verifiers are defined per team or application.
Routes are matched using segment-aware prefix matching. The route path defines the prefix to match, and requests to that path (and any subpaths) are forwarded.
Matching rules:
- Exact matches are tried first
- Prefix matches require a segment boundary (path separator
/) - A route with
path: /hooksmatches/hooksand/hooks/github, but NOT/hookshot
Path construction when forwarding:
| Route path | Destination | Request path | Forwarded to |
|---|---|---|---|
/hooks |
http://backend/api |
/hooks |
http://backend/api |
/hooks |
http://backend/api |
/hooks/github |
http://backend/api/github |
/ |
http://backend/api |
/events |
http://backend/api/events |
By default, gatekeeper sets the Host header of forwarded requests to match the destination hostname. Some backend applications need to see the original Host header. Enable preserve_host on a route:
routes:
- hostname: webhooks.example.com
path: /events
destination: http://internal-service:8080/webhooks
preserve_host: true # Backend receives Host: webhooks.example.comA webhook proxy is an attack surface on your internal network. Any service accepting traffic from the internet must be treated with care. Gatekeeper is designed to minimize this attack surface through multiple layers of validation before any request reaches your internal services.
Gatekeeper filters incoming requests by source IP before any other processing. Allowlists can be configured as static CIDR ranges or fetched dynamically from provider IP range endpoints.
Static allowlist:
ip_allowlists:
internal:
cidrs:
- "10.0.0.0/8"
- "192.168.0.0/16"Dynamic allowlist (automatically refreshed):
ip_allowlists:
aws:
fetch_url: "https://ip-ranges.amazonaws.com/ip-ranges.json"
fetch_jq: ".prefixes[].ip_prefix"
refresh_interval: 24hWhen running behind an ingress controller or load balancer, gatekeeper must read the X-Forwarded-For header to determine the true client IP. This requires explicit opt-in via the --trust-x-forwarded-for flag. See docs/X_FORWARDED_FOR.md for configuration details and security considerations.
Most webhook providers sign their payloads using HMAC or similar algorithms. Gatekeeper verifies these signatures using provider-specific implementations that follow each provider's documented algorithm exactly.
Signature verification proves that:
- The payload originated from the claimed provider
- The payload has not been modified in transit
- (For providers with timestamp validation) The request is not a replay attack
For providers without a native signature scheme, the generic hmac verifier supports configurable algorithms (SHA256, SHA512) and encodings (hex, base64).
Some webhook providers use simpler authentication: a shared secret sent in a header. While less sophisticated than cryptographic signatures, header tokens still provide strong protection against attackers who do not possess the secret.
The api_key verifier supports this pattern. Combined with IP allowlists, token verification blocks unauthorized requests effectively.
Even with valid signatures, a compromised provider account or leaked signing key could allow an attacker to send malicious payloads that exploit vulnerabilities in your webhook handlers.
Payload schema validation provides defense against injection attacks and malformed input by validating the structure of incoming payloads against known-good schemas for each provider and event type. This limits the attack surface even if authentication is somehow bypassed.
How it works:
- Define validators in your configuration (using JSON Schema)
- Associate validators with routes
- After signature verification passes, gatekeeper validates the payload against the schema
- Invalid payloads are rejected with HTTP 400 before reaching your backend
Pre-built schemas are included in the schemas/ directory for common providers (Slack, GitHub, Shopify). You can also provide inline schemas or create custom schema files for your specific event types.
Relay mode eliminates inbound firewall rules entirely. The relay client initiates all connections outbound from your private network. This means:
- No listening ports exposed to the internet on your internal network
- Webhooks must pass all configured validations (IP, signature, schema) before the relay client ever sees them
- The only attack surface is gatekeeperd itself, which performs all validation before queuing
Gatekeeper includes AI skills for interactive configuration. These skills guide you through setup step-by-step, generating complete configuration files with provider-specific instructions.
| Skill | Description |
|---|---|
| Configure Route | Configure a single webhook route - walks through provider selection, delivery mode, verifier setup, and generates both server and relay configs |
| Configure Helm | Configure a complete Kubernetes deployment - wraps multiple routes plus ingress/gateway, TLS, secrets, and relay setup |
With Claude Code (slash commands):
/configure-route
/configure-helm
With any AI assistant (paste the skill or ask directly):
I want to configure a webhook for Slack
Help me deploy gatekeeper to Kubernetes
The skill definitions in agents/ can be provided to any AI assistant as context for interactive configuration.
Configure Route supports all verifier types:
- Slack (HMAC-SHA256 with replay protection)
- GitHub (HMAC-SHA256)
- Shopify (HMAC-SHA256 base64)
- Google Calendar (header token)
- Microsoft Graph (JSON field token)
- Generic HMAC (configurable algorithm/encoding)
- API Key, Query Parameter, Header Query Parameter
Configure Helm handles:
- Multiple webhook routes in a single deployment
- Ingress (nginx, Traefik) or Gateway API configuration
- TLS with cert-manager or built-in ACME
- Secrets management (Helm-managed or external)
- Redis/Valkey for multi-replica relay coordination
- gatekeeper-relay deployment for private networks
The skill definitions are in the agents/ directory:
Convoy (github.com/frain-dev/convoy) is an open-source webhook gateway, but it solves the opposite problem: Convoy is for sending webhooks, not receiving them. It helps you deliver webhooks to your customers with retry logic, event persistence, and delivery tracking. If you are building a SaaS product that needs to notify customers via webhooks, Convoy is excellent.
Gatekeeper solves the receiving side: validating and forwarding incoming webhooks from providers like Slack and GitHub to your internal services.
General-purpose API gateways can terminate TLS and route traffic, but they do not have built-in support for provider-specific webhook signature verification. You would need to write custom plugins for each provider, at which point you have recreated this project with more complexity.
AWS API Gateway and Google Cloud Endpoints can handle webhook ingestion, but they introduce vendor lock-in and do not solve the fundamental problem: you still need to validate signatures, and you still need to route traffic to internal services that may not be reachable from the cloud provider's network.
Smee.io is a webhook relay service created by the Probot team for GitHub App development. It receives webhooks at a public URL and forwards them to a local client via Server-Sent Events.
The relay concept is similar to gatekeeper's relay mode, but Smee is a pure pass-through. It performs no signature verification, IP filtering, or payload validation. Anyone who discovers your channel URL can send arbitrary payloads to your application. Smee channels are also not authenticated, so webhook contents are visible to anyone with the channel ID.
Smee is explicitly intended for development only and is hosted as a third-party service. Gatekeeper provides proper provider-specific signature verification, is self-hosted, and supports both development and production use cases.
General-purpose tunnel services expose your localhost to the internet via a public URL. They are useful for development and testing, but they perform no webhook-specific validation. Any traffic that reaches the public URL is forwarded to your application.
These tools solve network reachability but not authentication. Any payload that arrives at the tunnel endpoint is forwarded directly into your private network, where your application must validate it. This means potentially malicious traffic enters your network before you can reject it.
Gatekeeper validates in the cloud before forwarding. Invalid signatures, disallowed IPs, and malformed payloads are rejected at the edge. Only authenticated requests reach your private network. This gives security teams confidence that the traffic crossing the perimeter has already been verified.
Webhook Relay (webhookrelay.com) is a commercial relay service with a self-hosted option. It can forward webhooks to private networks and provides a Lua scripting layer for custom logic.
However, Webhook Relay does not include built-in provider-specific verification. To validate signatures, you must write custom Lua functions for each provider, implementing the correct header parsing, algorithm, and comparison logic yourself. This is similar to the API gateway approach: possible, but you end up reimplementing provider-specific verification from scratch.
Gatekeeper provides built-in verifiers for each supported provider. You specify type: slack or type: github and the correct algorithm is applied automatically.
See docs/PROVIDER_TODO.md for the list of webhook providers we plan to support.
See docs/INSTALL.md for installation options:
- Docker images from GitHub Container Registry
- Helm charts for Kubernetes
- Building from source
- Kubernetes manifests (kustomize)
See docs/USAGE.md for detailed command-line usage:
gatekeeperdflags and environment variablesgatekeeper-relayconfiguration- Configuration file reference
- Endpoint documentation
- Interactive route configuration with Claude Code (
/configure-route)
See docs/DEVELOPMENT.md for:
- Development environment setup
- Running locally (server only, with relay, with real signatures)
- Docker and Kubernetes testing
- CI pipeline
See docs/TLS.md for TLS configuration:
- Kubernetes with Ingress and cert-manager (recommended for production)
- Baremetal with built-in ACME/Let's Encrypt
We welcome contributions:
- New provider implementations - See docs/PROVIDER_TODO.md for the wishlist and docs/PROVIDER_DEVELOPMENT.md for a step-by-step guide to developing providers
- Bug fixes - Please include a test case that reproduces the bug
- Performance improvements - Include benchmarks showing the improvement
- New features - Open an issue first to discuss the design
Before submitting:
- Read docs/DEVELOPMENT.md for setup and workflow
- Read docs/CODING_STANDARDS.md for code style and testing requirements
- All PRs must pass CI with 100% test coverage
A note on AI-assisted contributions: We use AI tools in our own development and welcome others who do the same. However, PRs must demonstrate human understanding of the changes. Include clear motivation explaining why the change is needed, not just what it does. Explain your testing approach. Low-effort submissions that appear to be unreviewed AI output will be declined. We value quality over quantity.