Webhook delivery

Manage connections under Connections > Webhook delivery. A delivery is transport configuration, not a provider identity or permission grant. Standalone and Overwolf use the same provider logic. Only live Overwolf game events require Overwolf.

The creation/edit dialog changes its setup steps, URL examples and field hints when you change Delivery type. Examples are placeholders, not working endpoints; replace them with URLs from your own tunnel or relay. Changing the type clears the previous type’s endpoint fields to avoid mixing incompatible settings.

All screenshots below use synthetic names and placeholder URLs. No accounts, credentials or live channels are shown. Creating a delivery does not create a public endpoint: obtain that endpoint from a tunnel service or relay first.

Default: the webhook gateway

Kick accounts use the operator-managed webhook gateway by default. The gateway receives and verifies Kick webhooks and forwards them to the app, so Kick sources on a gateway account need no delivery here. Their Webhook delivery field shows Webhook gateway (default).

Use the deliveries on this page only when you choose an alternative: switch the Kick account’s Connection to Self-hosted Kick companion under Connections > Accounts, then pick a Cloudflare/ngrok HTTP tunnel, a Raw-body relay or, for unverified generic events, Smee.

Where is the Kick companion?

It is the Node.js script apps\kick-companion\kick-companion.cjs in the Stream Pulse repository, not a separate Kick product or an automatically started part of the app. The current build has no companion installer/executable.

For HTTP tunnel and Raw-body relay Kick delivery, install Node.js 22.12 or newer, obtain/extract the repository, and open PowerShell in its root folder. Set your Kick application credentials and public HTTPS URLs, then run:

pnpm kick:companion   # or: node .\apps\kick-companion\kick-companion.cjs

Read Get and run the Kick companion for the complete download steps, secret prompt, copyable PowerShell configuration, expected startup output and local status check. Keep its terminal open while using Kick, alongside your separate tunnel process. The companion normally listens at http://127.0.0.1:5180; that local address is the Companion origin, not a public webhook URL.

The generic Smee source does not require the companion.

Choose an endpoint service

Resource Use in Stream Pulse What you need to supply
Cloudflare Tunnel HTTP tunnel A tunnel running on the companion machine, a public hostname, and route restrictions. A named tunnel is preferable for a stable OAuth/webhook URL.
Cloudflare Quick Tunnels Temporary HTTP tunnel experiments A temporary hostname and a path-restricted origin/proxy. Hostnames change on restart; there is no uptime guarantee. Quick Tunnels do not support SSE, so do not use them to host the Raw relay stream URL.
ngrok quickstart and webhook guide HTTP tunnel An installed/configured agent, your assigned HTTPS endpoint, and an edge policy or local proxy allowing only /webhook and /callback. Follow current account and plan requirements.
smee.io Smee, unverified only Create a channel and copy its full URL. No local companion or separate Smee CLI is needed for this source.
Your own compatible HTTPS/SSE service Raw-body relay An incoming webhook URL and an outgoing stream URL implementing the protocol below. Stream Pulse does not currently provide a hosted compatible relay or recommend a verified public relay provider.

Tunnels and relays are different. A tunnel forwards original requests to your local companion; a raw relay queues original bytes for the companion to read. An arbitrary webhook inspection URL, standard Smee channel, or JSON-rewriting forwarder is not a compatible verified raw relay.

Keep the companion and tunnel/relay processes running. Do not put an interactive login challenge in front of /webhook: Kick cannot complete it. Leave provider signature checks enabled, and never publish the simulator or companion control APIs. Tunnel and relay operators may see payloads; inspect their privacy and retention policies before sending private events.

HTTP tunnels

Create an HTTP tunnel delivery with:

  • The local Companion origin, normally http://127.0.0.1:5180.
  • The Public webhook URL registered in the provider’s application settings.

For example, if your tunnel hostname is your-tunnel.example, enter https://your-tunnel.example/webhook as Public webhook URL and route that path to http://127.0.0.1:5180/webhook. The companion origin remains http://127.0.0.1:5180, not the public tunnel hostname. The OAuth redirect would be https://your-tunnel.example/callback, not the webhook URL.

Run a tunnel such as Cloudflare Tunnel or ngrok yourself. Forward only /webhook and the separate OAuth /callback route to the companion. Do not expose /local/*, /desktop/*, or the simulator server. The companion rejects forwarded control requests as an additional protection.

Test endpoints registers the delivery with the companion and checks that its public endpoints answer:

  • HTTP tunnel: the companion sends a one-time probe to the public webhook URL and confirms it arrives back at the same companion. It also reports when the URL does not match the companion’s KICK_WEBHOOK_URL.
  • Raw relay: the companion waits for the relay stream to connect and checks that the public webhook URL answers.
  • Smee: the app opens the channel’s event stream once.

A passing test does not prove that the provider’s app configuration matches. Send a synthetic test through your provider only when you are ready.

For Kick, set KICK_WEBHOOK_URL to this public webhook URL (it defaults to KICK_PUBLIC_URL plus /webhook), register that exact URL in Kick, and select the delivery in the Kick source. The account and delivery must use the same companion. Existing Kick sources without an explicit delivery retain the legacy companion tunnel setup.

Example: fill in the HTTP tunnel delivery

HTTP tunnel delivery with a synthetic companion origin and public webhook URL

Setting Example
Name Example Kick tunnel
Delivery type HTTP tunnel
Companion origin http://127.0.0.1:5180
Public webhook URL https://your-tunnel.example/webhook
Companion KICK_PUBLIC_URL https://your-tunnel.example
Companion KICK_WEBHOOK_URL https://your-tunnel.example/webhook
Kick app webhook URL https://your-tunnel.example/webhook
Kick app OAuth redirect URL https://your-tunnel.example/callback

Replace your-tunnel.example with the hostname your tunnel actually supplies. The first four rows are delivery fields; the environment variables and Kick app settings are configured separately, not by saving this form.

Example: restrict a named Cloudflare Tunnel

Follow Cloudflare’s locally managed tunnel setup to install cloudflared, create/authenticate the tunnel, and route a hostname you control to it. Use its actual tunnel ID, credential-file path and hostname in a local configuration such as:

tunnel: YOUR_TUNNEL_ID
credentials-file: 'C:\Users\YOUR_USER\.cloudflared\YOUR_TUNNEL_ID.json'

ingress:
  - hostname: your-tunnel.example
    path: '^/(webhook|callback)$'
    service: http://127.0.0.1:5180
  - service: http_status:404

The anchored path rule forwards only the two public routes, preserving their paths. Everything else returns 404 at the tunnel, including /local/* and /desktop/*. Keep the credentials file private and out of source control. See Cloudflare’s configuration reference for ingress matching and validation.

For a configuration saved at $HOME\.cloudflared\config.yml, run in PowerShell:

cloudflared tunnel --config "$HOME\.cloudflared\config.yml" ingress validate
cloudflared tunnel --config "$HOME\.cloudflared\config.yml" run YOUR_TUNNEL_ID

In another terminal, with your Kick client ID and secret already configured as described in Kick companion:

$env:KICK_PUBLIC_URL = 'https://your-tunnel.example'
$env:KICK_WEBHOOK_URL = 'https://your-tunnel.example/webhook'
node .\apps\kick-companion\kick-companion.cjs

For ngrok, follow its setup links above and apply equivalent path restrictions using Traffic Policy and the Deny action, or a path-restricted local reverse proxy. Do not simply publish the whole companion port. Use the resulting ngrok HTTPS hostname in place of your-tunnel.example in all matching settings.

Finish and verify

  1. Register the exact webhook and redirect URLs in your Kick developer app.
  2. Save this delivery. In Overwolf, pair the companion as described below.
  3. Sign in through Connections > Accounts and select this delivery in the Kick source. The account must use the same local companion origin.
  4. Select Test endpoints. It should report that the public webhook URL reaches this companion.
  5. Keep Stream Pulse in Dry run in standalone and send a provider test event, when available. Check the source status, event matching and Activity. A plain unsigned POST must not be accepted as a verified Kick event.

If the tunnel hostname changes, update the environment variables, Kick app webhook/redirect URLs and delivery URL together, then restart the companion. Do not treat OAuth callback errors as webhook errors: /callback and /webhook are separate routes with separate purposes.

Pair Overwolf with the companion

Overwolf’s file-origin requests cannot use the browser-only /local/* API. Instead:

  1. Start the companion locally. Copy its short-lived pairing code from the console, or use Generate code for Overwolf in the trusted standalone browser page.
  2. In Overwolf, open the Kick account’s sign-in settings or a delivery’s Pair companion section.
  3. Paste the code into Single-use pairing code and select Pair this app.
  4. Sign in to Kick, refresh saved accounts, configure sources and destinations.

Pairing grants app-level access to companion accounts and actions. Pair only your trusted local app. Codes expire after five minutes, are single-use, and failed attempts are rate limited. Pairing credentials survive app restarts in a separate local store and are never exported with configuration.

Revoke this app’s pairing revokes its credential. In the trusted browser, List paired apps lets you revoke individual desktop clients. Revocation also terminates their open event streams. A freshly installed/imported Overwolf app must pair again; account metadata is not authorization.

Browser control routes retain exact-origin checks. Desktop routes require a bearer credential for every operation: Origin: null by itself grants nothing.

Raw-body outbound relays

Use Raw-body relay when a compatible relay service receives HTTPS webhooks and exposes an outbound SSE stream. The companion opens the stream, so no public webhook listener on your computer is needed. A separately reachable OAuth callback is still required for Kick sign-in.

Enter the provider-facing Public webhook URL and the Raw relay stream URL. These URLs usually differ. Stream Pulse supplies the consumer adapter; it does not deploy or operate a relay service.

For example, Public webhook URL might be https://relay.example/hooks/your-channel, while Raw relay stream URL is https://relay.example/streams/your-channel. Use the real incoming-webhook and outgoing-SSE URLs supplied by your relay operator; these example paths are not a requirement. Register the incoming URL with Kick and set KICK_WEBHOOK_URL to that exact value. Keep the companion origin local.

Example: fill in the raw-body relay delivery

Raw-body relay delivery with separate synthetic incoming and stream URLs

Setting Example
Name Example raw-body relay
Delivery type Raw-body relay
Companion origin http://127.0.0.1:5180
Public webhook URL https://relay.example/hooks/your-channel
Raw relay stream URL https://relay.example/streams/your-channel
Companion KICK_WEBHOOK_URL and Kick app webhook URL https://relay.example/hooks/your-channel
Companion KICK_PUBLIC_URL https://your-callback-tunnel.example
Kick app OAuth redirect URL https://your-callback-tunnel.example/callback

These are illustrative relay paths, not a deployed service. Obtain both relay URLs from your own compatible implementation or operator. The stream URL must return Content-Type: text/event-stream; it is not the URL to which Kick POSTs. If a service only supplies a webhook inbox or parsed JSON stream, it does not meet this contract.

Configure the companion, for example:

$env:KICK_PUBLIC_URL = 'https://your-callback-tunnel.example'
$env:KICK_WEBHOOK_URL = 'https://relay.example/hooks/your-channel'
node .\apps\kick-companion\kick-companion.cjs

Keep the callback tunnel path-restricted as above (only /callback is needed when the relay handles webhooks), configure Kick client credentials separately, and register the exact redirect and webhook URLs in the Kick app. Save the delivery, pair if using Overwolf, and select it in the Kick source. Check the delivery status for stream connection errors. A connected SSE stream does not prove that provider signatures, subscriptions or OAuth are configured.

Required relay message format

The relay must preserve the original bytes. Each default SSE message’s data contains one JSON object:

{
  "version": 1,
  "id": "unique-delivery-id",
  "headers": {
    "kick-event-message-id": "original-message-id",
    "kick-event-subscription-id": "original-subscription-id",
    "kick-event-message-timestamp": "original-timestamp",
    "kick-event-signature": "original-base64-signature",
    "kick-event-type": "chat.message.sent",
    "kick-event-version": "1"
  },
  "bodyBase64": "base64-of-original-request-bytes"
}

Send heartbeats while idle. The adapter limits frame/body sizes, reconnects with backoff and cancels streams when delivery configuration is removed. Only original Kick event/signature headers are accepted by the current provider adapter. Decoded bytes pass through the same signature, timestamp, replay and subscription checks as direct webhooks. Relay IDs never substitute for provider verification.

The shared transport is intended for future GitHub HMAC verification too; GitHub-specific accounts and event adapters are not implemented yet.

The JSON above is the content of an SSE data: message, terminated by a blank line. Its descriptive signature/body placeholders are not valid signed events. Preserve the provider’s actual headers and raw request bytes; do not generate bodyBase64 by parsing and reserializing JSON. For SSE framing/background, see MDN: Using server-sent events.

Smee: unverified generic events

  1. Obtain a channel at smee.io or a compatible server.
  2. Create a Smee delivery with its HTTPS channel URL.
  3. Add a Smee (unverified webhooks) source and choose that delivery.

For example, Smee channel URL looks like https://smee.io/your-channel-id. Copy the actual channel URL from Smee and use that same URL for your webhook sender. Unlike the other delivery types, Smee has no companion origin or separate public-webhook/stream URL fields.

Example: fill in the Smee delivery

Smee delivery with a synthetic channel URL and unverified-event warning

Setting Example
Name Example Smee channel
Delivery type Smee (UNVERIFIED generic events)
Smee channel URL https://smee.io/your-channel-id
Sender’s webhook URL The same actual Smee channel URL
Source type Smee (unverified webhooks)
Source delivery Select Example Smee channel

Select Start a new channel on smee.io, copy the resulting URL, and replace the example above. Save the delivery, create/select the Smee source, and keep standalone in Dry run. There is no companion sign-in, OAuth redirect or pairing step for this generic source.

After you have enabled the source, send a synthetic example from PowerShell:

$channel = 'https://smee.io/REPLACE_WITH_YOUR_ACTUAL_CHANNEL_ID'
Invoke-WebRequest -Method Post -Uri $channel `
  -ContentType 'application/json' `
  -Headers @{ 'X-GitHub-Event' = 'issues' } `
  -Body '{"action":"opened","issue":{"title":"Synthetic webhook example"}}'

This sends public, synthetic test data to a third-party relay, not to GitHub or Kick. It deliberately demonstrates why the header does not authenticate the sender. Do not paste credentials, private payloads or real account data into the example.

Match a rule against game Smee (unverified webhooks), feature unverified and event issues. Expect data.body.action to be opened and data.verified to be false. Unmatched source events are counted but do not produce a rule run in Activity. Use a matching dry-run rule to inspect the example without sending destination actions.

The source connects directly by SSE in either app mode. No companion is required for this generic source. Events use game source:smee, feature unverified, and event name x-github-event, kick-event-type, or webhook. The original parsed JSON body appears in data.body; data.verified is always false. Match nested payloads with conditions/templates, for example ``.

Standard Smee parses and reconstructs JSON. Provider signatures cover the original bytes, so it cannot be selected as verified Kick delivery. Never disable provider verification to work around this limitation.

Smee events can trigger real actions in Live mode, but are not authenticated provider events. Use only a trusted channel, suitable rules and cooldowns. The relay operator receives webhook content, and anyone with the channel URL may be able to listen. Do not send secrets or real private webhook data in tests.

Storage and portability

Delivery names, IDs and companion origins are exported. Public webhook and relay/channel URLs are stored locally but removed from exports because their paths can contain secrets. Re-enter these URLs after importing configuration. Pairing credentials, OAuth secrets and refresh tokens are never configuration fields.

Internal design decisions are kept in the repository’s docs/plans/ folder and are not published on this site.