Examples

Real-world configurations, ready to adapt. The full annotated config shows every field.

The standard PR feedback loop

One self-updating summary comment + granular required checks + lifecycle labels:

scm:
  github:
    - name: github
      enabled: true
      auth:
        secretRef:
          name: github-token
      actions:
        - name: task-checks
          type: commit_status
          enabled: true
          context_per_task: true            # tekton/ci/build, tekton/ci/test, ...
          when: 'isTaskRun() && !isFinallyTask()'

        - name: pipeline-check
          type: commit_status
          enabled: true
          when: 'isPipelineRun()'

        - name: pr-summary
          type: pr_comment
          enabled: true
          mode: upsert                      # one comment, updated in place
          when: 'isPR() && isPipelineRun() && stateIn("running", "success", "failure")'
          template: |
            ### {{ ternary "πŸ”„" (ternary "βœ…" "❌" (eq .State "success")) (eq .State "running") }} Pipeline `{{.PipelineName}}` β€” {{.State}}
            **Commit:** `{{ .CommitSHA | trunc 8 }}` Β· {{ if .TargetURL }}[logs]({{.TargetURL}}){{ end }}

        - name: ci-labels
          type: label
          enabled: true
          when: 'isPR() && isPipelineRun() && stateIn("running", "success", "failure", "error")'
          labels:
            add: ["ci::{{.State}}"]
            remove: ["ci::running", "ci::success", "ci::failure", "ci::error"]

Production failures β†’ Slack, everything else silent

notifiers:
  slack:
    - name: prod-alerts
      enabled: true
      secretRef:
        name: slack-webhook
      channel: "#prod-alerts"
      when: 'event.Namespace == "production" && isPipelineRun() && stateIn("failure", "error")'
      template: |
        :rotating_light: *{{.PipelineName}}* failed in *{{.Namespace}}*
        Run `{{.RunName}}` Β· Commit `{{ .CommitSHA | trunc 8 }}`
        {{if .TargetURL}}<{{.TargetURL}}|View logs>{{end}}

Deploy visibility: Environments + Grafana + Sentry

scm:
  github:
    - name: github
      enabled: true
      auth:
        secretRef:
          name: github-token
      actions:
        - name: deployments
          type: deployment_status
          enabled: true
          when: 'isPipelineRun() && event.PipelineName.startsWith("deploy-")'

notifiers:
  grafana:
    - name: deploy-markers
      enabled: true
      url: https://grafana.company.example.com
      token:
        secretRef:
          name: grafana-token
      when: 'isPipelineRun() && event.PipelineName.startsWith("deploy-") && stateIn("success", "failure")'
  sentry:
    - name: sentry
      enabled: true
      org: acme
      projects: ["api"]
      token:
        secretRef:
          name: sentry-token
      when: 'isPipelineRun() && event.PipelineName.startsWith("deploy-")'

Set the environment per run with the tekton.dev/tekton-events-relay.scm.context annotation (staging, production, …).

OAuth2 client credentials (webhook & Jira)

The generic webhook notifier and Jira support OAuth2 under auth.oauth2 β€” the relay fetches the access token and refreshes it before expiry, so the pod never 401s on a stale token. (Webhook works against any OAuth2-protected endpoint; Jira supports it natively β€” Cloud service accounts via https://auth.atlassian.com/oauth/token, Data Center via its OAuth2 provider. Grafana/Sentry are not included β€” their APIs use a service-account / auth token, not OAuth2 client credentials.)

notifiers:
  webhook:
    - name: oauth2-endpoint
      enabled: true
      url:
        secretRef:
          name: endpoint-url
      auth:
        type: oauth2
        oauth2:
          # grant_type: client_credentials   # default
          client_id:
            secretRef:
              name: endpoint-oauth2          # key: client_id
          client_secret:
            secretRef:
              name: endpoint-oauth2          # key: client_secret
          token_url: https://auth.company.example.com/oauth/token
      when: 'isPipelineRun() && stateIn("success", "failure")'

grant_type defaults to client_credentials. The relay exposes no ingress/redirect, so it cannot run the interactive authorization_code flow; for providers that only bootstrap that way, obtain a refresh_token out of band (one-time consent) and let the relay rotate access tokens with grant_type: refresh_token:

      auth:
        type: oauth2
        oauth2:
          grant_type: refresh_token
          client_id:
            secretRef:
              name: endpoint-oauth2
          client_secret:
            secretRef:
              name: endpoint-oauth2
          refresh_token:
            secretRef:
              name: endpoint-oauth2          # key: refresh_token (seeded out of band)
          token_url: https://auth.company.example.com/oauth/token

Not using OAuth2? The static token/credential files for the webhook, grafana, sentry and jira notifiers are re-read on every request, so rotating the Kubernetes Secret takes effect without restarting the pod.

Exporting to Apache DevLake

Engineering metrics (DORA & friends) belong in DevLake β€” feed it with the webhook notifier and a gojq transform matching DevLake’s deployments webhook schema:

notifiers:
  webhook:
    - name: devlake-deployments
      enabled: true
      url:
        secretRef:
          name: devlake-webhook            # https://devlake/.../plugins/webhook/<id>/deployments
      when: 'isPipelineRun() && event.PipelineName.startsWith("deploy-") && stateIn("success", "failure")'
      transform: |
        {
          deploymentCommits: [{
            repoUrl: ("https://github.com/" + .repo.owner + "/" + .repo.name),
            refName: .commit_sha,
            startedDate: .started_at,
            finishedDate: .finished_at
          }],
          id: .run_id,
          result: (if .state == "success" then "SUCCESS" else "FAILURE" end),
          startedDate: .started_at,
          finishedDate: .finished_at
        }

Multi-platform simultaneously

Instances are independent β€” one event can update GitHub and a self-managed GitLab and page on-call:

scm:
  github:
    - name: github
      enabled: true
      auth:
        secretRef:
          name: github-token
      actions: [{ name: status, type: commit_status, enabled: true }]
  gitlab:
    - name: gitlab-internal
      variant: self-managed
      enabled: true
      base_url: https://gitlab.corp.example.com/api/v4
      auth:
        secretRef:
          name: gitlab-token
      actions: [{ name: status, type: commit_status, enabled: true }]

notifiers:
  pagerduty:
    - name: oncall
      enabled: true
      integration_key:
        secretRef:
          name: pd-key
      severity: critical
      when: 'event.Namespace == "production" && stateIn("failure", "error")'

Each run picks its target via the scm.provider annotation (github or gitlab-internal).

Pipeline summary with the accumulator

Batch all TaskRun results into a single PR comment posted when the PipelineRun finishes:

accumulator:
  enabled: true
  ttl: 5m
  max_size: 200
  provider:
    name: github          # a registered pr_comment-capable instance

scm:
  github:
    - name: github
      enabled: true
      auth:
        secretRef:
          name: github-token
      actions:
        - name: pr-comment
          type: pr_comment
          enabled: true
          mode: upsert     # summaries converge to one comment