Quickstart
Five steps to create a project, connect the repository, issue an API key, report results from CI, and verify the data arrived.
1. Create the project
Every project belongs to an organization. Create one by navigating to Projects > New project in the web app.
- Name (required, up to 80 characters)
- Description (optional, up to 500 characters)
- Technologies (optional, populated automatically when connecting a repository in the next step)
A connected repository is not required to create a project. Linking a repository, generating an API key, and configuring CI reporting operate independently and can be completed in any order.
Note: A newly created project initializes with empty suites, no runs, and an unconnected repository status until it receives its first payload.
2. Connect repository via SCM webhook
This step activates the code change pipeline. Qably receives repository updates through webhooks sent by your Git host, completely independent of the project API key in step 3.
Repository selection
- Sign in with GitHub if you have not done so already. Qably uses this OAuth token to query accessible repositories across personal accounts and organizations.
- In project settings under Integrations, select the target repository from the list, sorted by most recent push.
- Selecting an unlinked repository creates an organization connection and generates a webhook secret. Selecting an already connected repository reuses its existing connection.
Registering the webhook with the provider
Qably does not register webhooks automatically on external hosts. Add the webhook manually in repository settings.
- Retrieve the secret by calling
POST /connections/:id/webhook-secretfrom connection settings. The response outputs the raw secret once upon creation or rotation, so copy it immediately. - In GitHub, navigate to Settings > Webhooks > Add webhook within the repository.
- Payload URL:
https://api.qably.dev/webhooks/scm/github - Content type:
application/json - Secret: the value generated in the previous step
- Events: select
pushat a minimum. Enable pull request events as well to capture complete branch activity.
Signature check: Qably validates every incoming payload against the secret using HMAC-SHA256 signatures (x-hub-signature-256 header in sha256=<hex> format). Missing or mismatched signatures are rejected with HTTP 401.
Checking delivery: Successful deliveries appear in your provider webhook delivery logs. In Qably, the updated ingestion batch, commits, and file diffs populate after the next push or pull request.
3. Issue an API key
This step configures the test results pipeline, supplying machine credentials so CI jobs can push test data without an interactive user session.
- Under project API Keys settings, create a key with an identifying name like "CI/CD Pipeline". This requires owner or admin permissions within the organization.
- The generated token follows the format
qbly_<lookupId>_<secret>and appears only once in the confirmation dialog. Qably stores solely its SHA-256 hash and cannot display it again. - Store the value in your CI provider. In GitHub Actions, navigate to Settings > Secrets and variables > Actions > Secrets and create the
QABLY_API_KEYrepository secret. If using a custom URL, add theQABLY_API_BASE_URLvariable under the Variables tab. Never commit plaintext tokens to source control.
Each API key is scoped strictly to a single project and only holds permissions to post test runs. It cannot read other projects, list suites, or modify organization configuration. The target project is determined directly from the key.
Revoking a key disables future requests immediately while preserving all historical runs linked to it. Projects can hold multiple active keys concurrently, allowing smooth key rotation without CI downtime.
4. Report results from CI
To connect Qably to your continuous integration pipeline, create a workflow file in your repository (such as .github/workflows/ci.yml). The YAML syntax structures the pipeline through on trigger events, runner environments under jobs, and an ordered sequence of commands in steps to prepare the runner, execute tests, and send the report.
For Jest, add jest-junit and set JEST_JUNIT_ADD_FILE_ATTRIBUTE to "true" so the XML includes source file paths for each test case, enabling Qably to map tests to code. Vitest has a built-in JUnit reporter: select it with --reporter=junit and set the file it writes with --outputFile, as in the workflow below.
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
name: Run tests and report to Qably
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
run: npm ci
- name: Run Jest tests
env:
JEST_JUNIT_OUTPUT_DIR: ./reports
JEST_JUNIT_OUTPUT_NAME: junit.xml
JEST_JUNIT_ADD_FILE_ATTRIBUTE: 'true'
run: npx jest --ci --reporters=default --reporters=jest-junit
- name: Report results to Qably
if: always()
env:
QABLY_API_KEY: ${{ secrets.QABLY_API_KEY }}
run: |
curl --fail --silent --request POST \
"https://api.qably.dev/runs/ingest/junit?externalId=gha-${{ github.run_id }}-${{ github.job }}&source=github_actions&commitSha=${{ github.event.pull_request.head.sha || github.sha }}" \
--header "Authorization: Bearer $QABLY_API_KEY" \
--header "Content-Type: application/xml" \
--data-binary @./reports/junit.xml || truename: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
name: Run tests and report to Qably
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
run: npm ci
- name: Run Vitest tests
run: npx vitest run --reporter=default --reporter=junit --outputFile=./reports/junit.xml
- name: Report results to Qably
if: always()
env:
QABLY_API_KEY: ${{ secrets.QABLY_API_KEY }}
run: |
curl --fail --silent --request POST \
"https://api.qably.dev/runs/ingest/junit?externalId=gha-${{ github.run_id }}-${{ github.job }}&source=github_actions&commitSha=${{ github.event.pull_request.head.sha || github.sha }}" \
--header "Authorization: Bearer $QABLY_API_KEY" \
--header "Content-Type: application/xml" \
--data-binary @./reports/junit.xml || trueImportant: The if: always() condition on the reporting step is required to guarantee delivery even when tests fail. CI build failures should reflect failing test assertions rather than being disrupted by telemetry transmission.
The POST /runs/ingest/junit endpoint receives raw XML and handles parsing server-side. Unrecognized suites or cases are provisioned automatically within that project.
The externalId parameter ensures idempotency across CI retries. Linking it to runner execution identifiers (such as github.run_id and github.job) ensures re-running a failed job in GitHub Actions updates the existing run rather than creating duplicate entries. The commitSha parameter lets Qably list the candidate commits between two runs. On pull requests the expression uses the head commit, because github.sha is a temporary merge commit that no push contains. See how to read a regression.
5. Verify the data arrived
Verify each pipeline separately, as one may function correctly while the other requires troubleshooting.
- Test results: open the project and locate the run submitted by CI. Overall run status is derived from individual test cases: any failing test fails the run; pending or executing tests keep it in progress; and it passes once at least one test passes or skips with no failures.
- Code changes: requires completing step 2. The Repository tab should display an ingestion batch matching the latest push or pull request.
Troubleshooting: If expected data does not appear, check the FAQ page for common issues such as unregistered webhooks or missing CI environment variables.