Kite in GitHub Actions
Use Kite inside a GitHub Actions workflow to receive webhook events during CI runs — from GitHub itself or any other source.
CI runners are ephemeral and not publicly addressable, so webhook sources can't reach them directly. Running kite proxy inside a job lets your test suite receive the same real webhook events your app sees in production — no tunnel, no mock.
This tutorial builds a workflow that spins up Kite, runs your tests against a real GitHub webhook stream, and tears down cleanly.
// 01Prerequisites
- A Kite account and an API key stored as the secret
KITE_API_KEY. - At least one webhook source registered on your Kite team (e.g.
kite endpoints create --source github).
// 021. Minimal workflow
name: integration-tests
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Kite
run: curl -fsSL https://getkite.sh/install | sh
- name: Start your app
run: |
npm ci
npm run start &
echo "APP_PID=$!" >> $GITHUB_ENV
# Wait for it to bind
until curl -fsS http://localhost:3000/health; do sleep 1; done
- name: Start Kite proxy
env:
KITE_API_KEY: ${{ secrets.KITE_API_KEY }}
run: |
kite proxy --source github --target http://localhost:3000/webhooks &
echo "KITE_PID=$!" >> $GITHUB_ENV
sleep 2
- name: Run tests
run: npm test
- name: Tear down
if: always()
run: |
kill $KITE_PID || true
kill $APP_PID || true// 032. Receive only the events the test cares about
Use CLI filters so the test run doesn't get noise from unrelated repos or sources:
kite proxy \ --source github \ --event-type com.github.pull_request.opened \ --target http://localhost:3000/webhooks
For heavier shaping, check in a ci.kite.json manifest and swap the command:
kite run --manifest ci.kite.json
// 043. Use --client-id for deterministic replay
Every subscriber is identified by a --client-id and keeps its own delivery cursor. Give each workflow a stable client ID so reruns pick up events that were live at the time:
kite proxy \
--source github \
--target http://localhost:3000/webhooks \
--client-id "gha-${{ github.run_id }}"Events delivered during the run are not re-played on the next run. If you need to *replay* a specific event during a flaky-test investigation, use kite retry — it pulls from the dead-letter queue.
// 054. Receive webhooks synthesised by the test itself
When the test *sends* a webhook and wants to assert the app received it, point the source at GitHub's repository events the workflow itself produces (issues opened, labels applied, etc.) and kite stream in parallel with the test:
- name: Capture Kite events for assertions
env:
KITE_API_KEY: ${{ secrets.KITE_API_KEY }}
run: |
kite stream --source github --json --client-id "gha-${{ github.run_id }}" \
> kite-events.ndjson &
echo "KITE_STREAM_PID=$!" >> $GITHUB_ENV
- name: Run tests
run: npm test
- name: Flush and assert
if: always()
run: |
kill $KITE_STREAM_PID || true
node ./ci/assert-events.mjs kite-events.ndjsonassert-events.mjs reads the NDJSON log and fails the job if an expected CloudEvent didn't arrive.
// 065. Tips
- Pin the Kite version in CI once you're past experimentation. Install with
KITE_VERSION=0.9.1 curl -fsSL https://getkite.sh/install | sh. - Separate API keys per workflow. Create a Kite API key scoped to each workflow so a leaked CI secret can be revoked without disrupting other jobs.
- Use `if: always()` to tear down. The
killstep must run even when tests fail, otherwise the runner hangs until the default 6-hour timeout.
// 07See also
- Deployment — using Kite in non-CI production containers
- CLI Reference — every flag on
kite proxy,kite stream, andkite retry