> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Shared Webhook Ingress

> Serve every webhook channel behind one gateway port at /webhooks/<channel>

Every `mode: webhook` channel is served through the gateway's single listener at `/webhooks/<channel>` — one port, one public URL, routed by path.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Shared Webhook Ingress"
        W[🌐 WhatsApp] --> G[🚪 Gateway :8765]
        B[🌐 Billing hook] --> G
        S[🌐 Stripe] --> G
        G --> R{🧭 Route by path}
        R -->|/webhooks/whatsapp| A1[🤖 Agent]
        R -->|/webhooks/billing_hook| A2[🤖 Agent]
        R -->|/webhooks/stripe| A3[🤖 Agent]
    end

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef gate fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef route fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff

    class W,B,S input
    class G gate
    class R route
    class A1,A2,A3 agent
```

## Quick Start

**1. Declare webhook channels — no port on any**

Leave `webhook_port` unset and every `mode: webhook` channel shares `gateway.port`.

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# gateway.yaml
gateway:
  port: 8765                  # the only public endpoint

channels:
  whatsapp:
    platform: whatsapp
    mode: webhook             # served at /webhooks/whatsapp
  billing_hook:
    platform: webhook
    mode: webhook             # served at /webhooks/billing_hook
```

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai gateway start --config gateway.yaml
```

**2. Opt out — pin one channel to its own port**

Set `webhook_port` on a channel to restore a standalone server for just that channel.

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# gateway.yaml
channels:
  legacy_hook:
    platform: webhook
    mode: webhook
    webhook_port: 9090        # standalone server on :9090 (opt-out)
```

***

## How It Works

An external service POSTs to `/webhooks/<channel>`; the gateway verifies the signature per-channel, then dispatches to the agent.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Service as 🌐 External service
    participant Gateway as 🚪 Gateway :8765
    participant Verifier as 🔐 Channel verifier
    participant Agent

    Service->>Gateway: POST /webhooks/whatsapp
    Gateway->>Verifier: verify(headers, raw_body)
    alt Signature invalid
        Verifier-->>Gateway: False
        Gateway-->>Service: 401 Invalid signature
    else Unknown path
        Gateway-->>Service: 404 Not found
    else Signature valid
        Verifier-->>Gateway: True
        Gateway->>Agent: run
        Agent-->>Gateway: reply
        Gateway-->>Service: 200 OK
    end
```

Each channel keeps its own verifier — a bad signature on one channel returns `401` without touching the others. An unknown path returns `404` with no cross-channel fallthrough. Hot-reloading a channel re-mounts `/webhooks/<channel>` automatically; removing a channel clears its mount — no process restart.

### When do I set `webhook_port`?

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Q[Multiple webhook channels?] --> D{Need standalone isolation?}
    D -->|No — one URL is simpler| SHARED["Leave webhook_port unset<br/>(shared listener, default)"]
    D -->|Legacy tunnel pinned to one port| STANDALONE["Set webhook_port<br/>(standalone server)"]
    D -->|Process isolation for one channel| STANDALONE

    classDef q fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef shared fill:#10B981,stroke:#7C90A0,color:#fff
    classDef standalone fill:#F59E0B,stroke:#7C90A0,color:#fff

    class Q,D q
    class SHARED shared
    class STANDALONE standalone
```

***

## Configuration Options

`webhook_port` selects shared vs. standalone mode on any `mode: webhook` channel.

| Option | Type | Default | Description |
| - | - | - | - |
| `webhook_port` | `Optional[int]` | `None` | `None` → served through the gateway's shared listener at `/webhooks/<channel>`. An `int` → binds a private standalone server (opt-out, backward-compatible) |

***

## Common Patterns

### Multi-provider on one URL

Point GitHub, Stripe, and a generic billing hook at one public URL — the path picks the channel.

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# gateway.yaml
gateway:
  port: 8765

channels:
  github:
    platform: webhook
    mode: webhook             # /webhooks/github
  stripe:
    platform: webhook
    mode: webhook             # /webhooks/stripe
  billing_hook:
    platform: webhook
    mode: webhook             # /webhooks/billing_hook
```

Provider dashboards get `https://bots.example.com/webhooks/github`, `.../webhooks/stripe`, `.../webhooks/billing_hook`.

### Reverse-proxy termination for one HTTPS URL

Terminate TLS at your proxy and forward one path prefix to the gateway.

```nginx theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# nginx
location /webhooks/ {
    proxy_pass http://127.0.0.1:8765;
}
```

### Explicit-port opt-out for a legacy tunnel

A tunnel already pinned to a fixed port keeps working — set `webhook_port` on that one channel.

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# gateway.yaml
channels:
  legacy_hook:
    platform: webhook
    mode: webhook
    webhook_port: 8080        # standalone server for a pinned tunnel
```

***

## Best Practices

> **Prefer the shared listener**
>
> One public URL is simpler to secure, monitor, and put behind a reverse proxy. Leave `webhook_port` unset unless you have a specific reason not to.

> **Set an HMAC secret per channel**
>
> The gateway delegates verification to each channel's own verifier, fail-closed — a bad signature returns `401`. Give every webhook channel its own `verify.hmac.secret`.

> **Don't set webhook\_port unless you need isolation**
>
> An explicit port opts a channel out of the shared listener and back onto a standalone server. Use it only for a legacy tunnel pinned to one port, or when one channel needs process isolation.

> **Rely on automatic re-mount after hot-reload**
>
> After a hot-reload, `/webhooks/<channel>` re-mounts automatically and a removed channel's mount is cleared — no restart required.

***

## Related

> **[Webhook Channel](https://praison.ai/docs/features/webhook-channel.md)**
>
> Route any HTTP webhook to an agent with a YAML route

> **[Webhook Verification](https://praison.ai/docs/features/webhook-verification.md)**
>
> The HMAC signature primitive each channel verifies fail-closed

> **[Gateway Overview](https://praison.ai/docs/features/gateway-overview.md)**
>
> Gateway architecture and the single listener

> **[Gateway Inbound Hooks](https://praison.ai/docs/features/gateway-inbound-hooks.md)**
>
> `/hooks/<path>` generic triggers — the ad-hoc counterpart to `/webhooks/<channel>`


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.