Skip to main content
This guide connects GitHub to Katilyst with a workflow file. When a champion merges a pull request carrying security work, the workflow posts the event to Katilyst and the champion earns credit. Unlike the no-code integrations, this one lives in your repository as a file your team reviews and versions like any other code. The whole integration is about twenty lines. The pattern applies to any GitHub event you can act on. How you recognize security work, and how you resolve a GitHub user to a Katilyst User, depend on how your organization works — treat the specifics below as a worked example.

Prerequisites

  • A Katilyst API key. An Account Admin generates one from the Admin Portal. Keys are shown once when generated, and a lost key cannot be recovered.
  • Permission to add workflow files and repository secrets in the repositories you are instrumenting.
  • A way to resolve GitHub users to email addresses. This is the part that needs thought — see below.

Resolve identity first

Katilyst identifies a User by email address. GitHub webhook payloads carry a username, and the email field is frequently empty because users can keep it private. Settle this before writing any workflow, because every other part is straightforward and this one is not. Three approaches, in the order most organizations land on them.
If your GitHub accounts are provisioned through SSO, usernames usually match the local part of a work email, so jsmith becomes jsmith@example.com.This is the simplest approach and it works well in GitHub Enterprise Cloud with SAML. It fails for anyone whose GitHub account predates the SSO rollout, and for contractors on personal accounts.
Store a small JSON or CSV file in the repository mapping GitHub usernames to email addresses, and look up the author in the workflow.This handles the exceptions the derivation approach misses, and it has the advantage of being reviewable. It needs maintaining as people join and leave, which is work somebody has to own.
If you run SCIM provisioning, your identity provider holds the authoritative mapping and can be queried at run time.The most robust option and the most work. Worth it when you are instrumenting many repositories and the mapping would otherwise drift.
Do not use the commit author email without checking it first. GitHub substitutes a users.noreply.github.com address when a user enables email privacy, and that address will not match anyone in your Katilyst Project.
The worked example below derives the email from the username, which is the right starting point for most teams.

Decide your three mappings

As with any integration, three choices determine how this is built. The third deserves the most thought. The author wrote the fix, the merger approved it, and a reviewer may have been the one who caught the issue. Your program decides which of those behaviours it wants more of.
Frequency limits configured on an Available Action do not apply to actions logged through the API, so the if condition on your job is the only thing limiting how often this workflow awards credit. A batch of fifty merges logs fifty actions. If that is realistic in your repository, constrain it in the workflow rather than relying on the limit set in Katilyst.

Configure the action in Katilyst

1

Create an Available Action

In Katilyst, create an Instance action named something recognizable, such as GitHub — Security Fix Merged. Set the Attribute it changes, the amount, and any frequency limit.
Instance actions award credit every time they are logged, which suits merged pull requests. See Core concepts for how this differs from State actions.
2

Find your project ID

Call List projects and note the id of the program you are integrating with.
3

Find your action ID

Call List a project’s available actions and note the id of the action you created.

Store the API key as a secret

In the repository, go to Settings → Secrets and variables → Actions → New repository secret. Name it KATILYST_API_KEY and paste the key as the value. Store the key at the organization level instead if you are instrumenting several repositories, so there is one place to rotate it.
GitHub encrypts secrets and masks them in run logs, so the key never appears in output. This is a real advantage over integrations that store credentials in a rule configuration where anyone with edit access can read them.

Add the workflow

Create .github/workflows/katilyst.yml in the repository.
.github/workflows/katilyst.yml

Why it is written this way

Two details are worth keeping if you adapt this. Untrusted values go through env, never directly into the shell. A pull request title is attacker-controlled text, and interpolating ${{ github.event.pull_request.title }} straight into a run: block lets anyone who can open a pull request execute commands on your runner. Passing it as an environment variable and referencing ${PR_TITLE} closes that. This is GitHub’s own guidance on script injection and it applies to any workflow that touches pull request content. jq builds the JSON rather than string interpolation. A title containing a quote or a backslash would produce malformed JSON and a confusing 400. jq escapes correctly and is preinstalled on GitHub-hosted runners. permissions: {} grants the job no access to the repository, which is all it needs since it never checks out code.

Adapting it

For several repositories, move the body of this workflow into a reusable workflow or a composite action and call it from each one, so there is a single place to update when the action ID or the identity rule changes.
If your contributions arrive as pull requests from forks, secrets are not available to workflows triggered by pull_request. Internal repositories where people branch within the same repository are unaffected. If forks are part of your flow, bring this to whoever owns your CI configuration rather than reaching for pull_request_target, which carries its own risks.

Verify

Open a pull request, apply your security label, and merge it.
  1. Open the Actions tab in the repository and find the run. A successful step prints nothing, since curl --silent stays quiet on success and --fail makes a non-2xx response fail the step.
  2. Call List a user’s logged actions and confirm the award landed.
Read the status field on the result. APPROVED means the reward applied.

Troubleshooting

  • The workflow never runs. The if condition did not match. Comment it out temporarily, merge a test pull request, and confirm the job fires at all before narrowing again. An unmatched condition produces no run and no error, which makes it easy to misread as a broken workflow.
  • The workflow succeeds and no reward appears. Read the action back and check its status. PENDING means the Available Action requires admin approval and is waiting for it, which is program configuration rather than a workflow fault. A LIMIT_EXCEEDED_TIMEFRAME record came from someone submitting inside the platform rather than from this workflow — frequency limits do not apply to API calls.
  • curl exits 22. --fail returning this means Katilyst rejected the request outright. Add --include temporarily to see the response body and status code in the run log. A 409 here means the User is not eligible for that action, which is a program rule rather than an integration fault.
  • A 400 response. Usually the derived email does not match a User in that Katilyst Project. Call List a project’s users and compare against what the workflow built.
  • A 401 response. The key is invalid, the secret is missing or misnamed, or the API-Key prefix was dropped from the header value. An invalid key is the most common cause — confirm it has not been rotated or revoked since the secret was stored.
  • The run log shows an empty email. PR_AUTHOR resolved to nothing, which usually means the event payload differs from what the workflow expects. Print the value to the log before the curl call to see what arrived.

Core concepts

Instance versus State actions, guardrails, and how rewards are decided.

Log an action

Full reference for the endpoint this workflow calls.