kite~/kite/docs
v0.2.1
Tutorial

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

yaml
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:

bash
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:

bash
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:

bash
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:

yaml
- 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.ndjson

assert-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 kill step 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, and kite retry