# Webhook development

> Receive real webhook deliveries from GitHub, Stripe, Slack, and others on your dev machine.

Webhook providers need a public HTTPS URL to deliver to. routeup gives your local
service one, so you can develop against real deliveries instead of mocking them.

## 1. Expose your local API

Run your API (say on port `8080`) and expose it:

Configure a public server first using
[Exposing publicly](/docs/getting-started/exposing-publicly).

```bash
routeup serve example-app --port 8080 --expose
```

The examples below assume your saved token allows
`*.team.routeup.dev`.

routeup prints the local URL, public URL, exposure scope, and targets:

```txt frame="terminal" title="terminal"
route: example-app
local: https://example-app.localhost
public: https://example-app.team.routeup.dev
expose: all paths
targets:
  /        http://localhost:8080

requests: live
press Ctrl-C to stop
```

## 2. Point the provider at it

Register the public URL as the webhook endpoint in the provider's dashboard:

```txt frame="terminal" title="terminal"
GitHub:  Settings → Webhooks → Payload URL
Stripe:  Developers → Webhooks → Add endpoint
Slack:   Your App → Event Subscriptions → Request URL
```

Use the full path your handler listens on, e.g.
`https://example-app.team.routeup.dev/api/webhooks/github`.

## 3. Watch deliveries

Tail public traffic as it arrives:

```bash
routeup logs example-app --public --follow
```

```txt frame="terminal" title="terminal"
TIME      ROUTE                         SOURCE  STATUS  ID                    METHOD   DURATION  PATH
12:41:03  example-app                   public  200     req_Ap7kQ3mN8vR2xLzC  POST     38ms      /api/webhooks/github
```

Because the route and namespace are configured, you request the same endpoint on
each run instead of accepting a newly generated URL. Public claims remain subject
to the server's normal conflict rules.

## 4. Inspect a specific delivery

`routeup logs` shows metadata only. To see the full payload and response for a
delivery, enable capture in `routeup.json`:

Stop the current route owner, add this config, then restart
`routeup serve --expose` before sending the delivery you want to inspect.

```json title="routeup.json"
{
  "name": "example-app",
  "port": 8080,
  "capture": {
    "request": true,
    "response": true,
    "redact_headers": ["x-hub-signature-256", "authorization"]
  }
}
```

In another terminal, or after stopping the follow command, grab the request id
from the log line and inspect it:

```bash
routeup inspect req_Ap7kQ3mN8vR2xLzC
routeup inspect req_Ap7kQ3mN8vR2xLzC --curl
```

The curl form produces a command for sending the captured request to the local
route. It includes every retained header. Signature or authorization headers
appear only when capture retained them; add them to `redact_headers` above when
they should not be stored or exported.

```txt frame="terminal" title="terminal"
Request req_Ap7kQ3mN8vR2xLzC

Metadata
--------
Source: public
Route: example-app
Target: /:8080
Status: 200
Duration: 38ms
Method: POST
Path: /api/webhooks/github
Host: example-app.team.routeup.dev

Request
-------
Capture: complete
Body bytes: 63
Redacted: authorization, x-hub-signature-256
X-Github-Event: push

Body
----
{
  "ref": "refs/heads/main",
  "repository": {
    "full_name": "acme/api"
  }
}

Response
--------
Capture: complete
Body bytes: 11
Redacted: authorization, x-hub-signature-256
Content-Type: application/json

Body
----
{
  "ok": true
}
```

## Scoping exposure

If you only want the webhook path reachable and the rest of the app private, you
can limit public exposure to a path prefix (for example `/api/webhooks/*`) so the
rest of the service stays local-only:

Restart the route owner after changing this setting.

```json title="routeup.json"
{
  "name": "example-app",
  "port": 8080,
  "expose": {
    "paths": ["/api/webhooks/*"]
  }
}
```
