> For the complete documentation index, see [llms.txt](https://docs.flxbl.io/flxbl/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.flxbl.io/flxbl/sfp-server/setting-up/webhook-ingress-for-private-servers.md).

# Webhook ingress for private servers

GitHub.com, Azure DevOps, and other public SaaS webhook providers must call a public HTTPS URL. SSH access, VPN access, bastion hosts, and `ProxyJump` only prove that an operator can administer the server; they do not prove that a webhook provider can deliver a payload.

Use this page when your sfp server backend is private but repository webhooks must still reach one public payload URL:

```
https://<public-webhook-host>/sfp/api/repository/webhook
```

The sfp server host should remain blocked from the public internet. Do not open the EC2 instance, VM, or container host directly to `0.0.0.0/0`. Only the ingress layer is public, and for webhook-only ingress it should forward only `POST /sfp/api/repository/webhook` to Caddy. Return `403` or `404` for other public paths unless you intentionally want to publish the full sfp UI/API through that same ingress.

Example public payload URLs:

| Pattern                             | Payload URL shape                                                                                                      |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Public ALB / customer load balancer | `https://sfp-webhook.example.com/sfp/api/repository/webhook`                                                           |
| Public reverse proxy / tunnel       | `https://sfp-webhook-tunnel.example.com/sfp/api/repository/webhook`                                                    |
| API Gateway                         | `https://<api-id>.execute-api.<region>.amazonaws.com/sfp/api/repository/webhook` or a custom domain with the same path |

## Recommended order

| Pattern                             | Best fit                                                                                                                   | Private backend control                                                                                                                           |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| Public ALB / customer load balancer | Default choice on AWS or when the customer already has a managed load balancer                                             | Backend security group allows only LB-to-server traffic; listener rule forwards only the webhook path unless the full app is intentionally public |
| Public reverse proxy / tunnel       | Server cannot accept inbound internet traffic                                                                              | Tunnel forwards to localhost or a private listener; proxy allows only the webhook path unless the full app is intentionally public                |
| API Gateway                         | You need API Gateway features such as custom domains, auth layers, quotas, central API management, or private integrations | HTTP API route exposes only the webhook path and forwards through VPC Link to an internal ALB/NLB                                                 |

Do not make GitHub.com source-IP allowlists the primary control. Provider IP ranges change, and source IP does not prove authenticity. Put a public HTTPS ingress in front of the server, keep the backend private to that ingress, and let sfp-server validate the webhook signature.

## Shared requirements

Every ingress pattern must preserve the request exactly enough for provider signature validation:

* Allow HTTPS `POST` to the exact webhook payload URL.
* Keep the sfp backend host private; the provider should never call the backend IP or private DNS name directly.
* Forward only `POST /sfp/api/repository/webhook` for webhook-only ingress.
* Forward the raw request body unchanged.
* Preserve provider headers such as `X-Hub-Signature-256`, `X-GitHub-Event`, and `X-GitHub-Delivery`.
* Set or preserve `X-Forwarded-Proto: https`, `X-Forwarded-Host`, and `X-Forwarded-For`.
* Return a `2xx` response quickly. sfp-server dispatches webhook work to Hatchet and returns without waiting for workflow completion.

Do not parse and re-serialize webhook payloads in the ingress layer. GitHub computes `X-Hub-Signature-256` over the original request body, so mapping templates, body rewrites, or missing signature headers can make valid deliveries fail validation.

## Public ALB / load balancer

This is the recommended default for AWS deployments and for customer environments that already operate a public load balancer.

Expected flow:

```
GitHub.com HTTPS webhook
  -> public ALB / customer LB TLS listener
  -> path rule for POST /sfp/api/repository/webhook
  -> private HTTP target group
  -> Caddy HTTP listener
  -> sfp-server
```

Required checks:

* Public DNS resolves to the load balancer.
* TLS certificate is valid for the webhook hostname.
* Target group health check passes against the Caddy listener.
* Backend security group allows traffic from the load balancer security group, not from the whole internet.
* Listener rules forward the webhook path and reject other public paths unless the full app is intentionally public.
* Forwarded headers are set or preserved.

When using `--tls-mode none`, Caddy still runs because it handles auth routing. It serves HTTP behind the load balancer instead of terminating TLS itself.

## Public reverse proxy / tunnel

Use a tunnel or managed reverse proxy when the server cannot accept inbound traffic from the internet.

Expected flow:

```
GitHub.com HTTPS webhook
  -> public proxy hostname
  -> path rule for POST /sfp/api/repository/webhook
  -> outbound tunnel or reverse proxy
  -> Caddy HTTP listener
  -> sfp-server
```

Required checks:

* Proxy hostname is stable and reachable over HTTPS.
* Proxy process is always running and supervised.
* Proxy allows the webhook path and rejects other public paths unless the full app is intentionally public.
* Proxy forwards the raw request body and provider signature headers unchanged.
* Proxy forwards original host/protocol headers or sets equivalent `X-Forwarded-*` headers.

Quick tunnels are acceptable for smoke testing, but do not use them as production webhook URLs. Production tunnels should be named, supervised, and bound to a stable hostname.

## API Gateway

Use API Gateway only when you need its API-management features. For plain webhook ingress, an ALB or managed tunnel is simpler to operate.

Expected flow:

```
GitHub.com HTTPS webhook
  -> API Gateway HTTP API
  -> route POST /sfp/api/repository/webhook
  -> VPC Link
  -> internal ALB/NLB listener
  -> Caddy HTTP listener
  -> sfp-server
```

Required checks:

* VPC Link status is `AVAILABLE` before testing deliveries.
* Private integration points to an internal ALB/NLB listener.
* VPC Link security group can reach the internal load balancer.
* Internal load balancer target group is healthy.
* API Gateway exposes a route for `POST /sfp/api/repository/webhook`.
* API Gateway stage/base path is stripped or mapped so Caddy receives `/sfp/api/repository/webhook`.
* Raw request body and provider signature headers are forwarded unchanged.

If API Gateway forwards the stage name to the backend, use the `$default` stage, a custom domain/base path mapping, or a request path override. The path received by Caddy must match the webhook payload URL configured in the provider.

## Verify delivery

Do not test only the root domain. Test the exact payload URL configured in the provider.

For GitHub, use **Recent deliveries** and **Redeliver** from the webhook settings page. A successful route should return `2xx`, and sfp-server logs should show that the webhook signature was verified and the event was received.

If a manual `POST` works but the provider delivery fails, compare:

* Public URL and path.
* HTTP method.
* TLS certificate validity.
* Provider signature header preservation.
* Raw body preservation.
* Backend path received by Caddy.

See [Troubleshooting → Webhooks](/flxbl/sfp-server/setting-up/setting-up-sfp-server/troubleshooting.md#webhooks) for common symptoms.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.flxbl.io/flxbl/sfp-server/setting-up/webhook-ingress-for-private-servers.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
