docs Webhooks & CI/CD
Webhooks & CI/CD
Automatic deployments triggered by Git pushes, pull requests and CI pipelines.
How Auto-Deploy Works
When DeployKit receives a push event, it looks for applications whose repository and branch match and queues a deployment for each one — exactly as if you had clicked Deploy. Pull request / merge request events create, update and clean up preview deployments. Preview apps themselves are never matched by push events. If nothing matches, the endpoint answers with an explanatory message rather than an error.
Endpoints
/api/webhooks/github/api/webhooks/gitlab/api/webhooks/genericRequests that fail verification get a 401. All three endpoints share a rate limit of 120 requests per minute per IP.
GitHub: Use the GitHub App
For github.com, the recommended setup is the GitHub App. It registers its own webhook, so connected applications need no per-repository webhook at all — the application's Webhook section simply says it is handled by the App. App deliveries are verified with the App's own webhook secret, and you also get commit statuses and PR comments.
GitHub: Repository Webhook
Without the App, add a webhook in your repository under Settings → Webhooks. The URL is also shown in the application's General tab:
- Payload URL: https://your-deploykit-domain/api/webhooks/github
- Content type: application/json
- Secret: the WEBHOOK_SECRET from your .env file
- Events: 'Pushes', plus 'Pull requests' for preview deployments
Apps without the App connection are matched by repository URL (case, a trailing slash and .git are ignored) and branch. A repository webhook keeps working after you register a GitHub App — both secrets are accepted. If you later connect the app through the App, the old webhook's duplicate delivery is ignored (connected apps match only by installation and repository id), so a push never deploys twice; you can delete it.
GitLab Webhooks
In your GitLab project, go to Settings → Webhooks and add:
- URL: https://your-deploykit-domain/api/webhooks/gitlab
- Secret token: the WEBHOOK_SECRET from your .env file
- Triggers: 'Push events', plus 'Merge request events' for preview deployments
Opening, updating or reopening a merge request deploys its preview; merging or closing it removes the preview.
Generic Webhook (CI, Bitbucket, Gitea, scripts)
Any system can trigger a deploy by posting a push-style JSON payload to /api/webhooks/generic with an X-Webhook-Token header. The branch comes from ref and the repository from repository.clone_url, repository.html_url or repository.links.html.href (Bitbucket). Every application matching that URL and branch is deployed — including apps connected through the GitHub App.
curl -X POST https://your-deploykit-domain/api/webhooks/generic \
-H "Content-Type: application/json" \
-H "X-Webhook-Token: $DEPLOYKIT_WEBHOOK_TOKEN" \
-d '{
"ref": "refs/heads/main",
"repository": { "clone_url": "https://github.com/acme/web.git" }
}'The token is either the global WEBHOOK_SECRET or an application's own webhook secret. A per-app secret (16–200 characters) is set through the API with application.update (webhookSecret); once set, that app only deploys with its own secret, so you can hand a CI pipeline access to one app and rotate it without touching the global secret. Optional fields after / head_commit.id and head_commit.message label the deployment; the deploy always builds the branch head.
Deploying from CI/CD
Auto-deploy on push usually makes CI integration unnecessary. To deploy only after tests pass, make sure the application does not also receive push events (no repository webhook, and not connected through the GitHub App — connected apps deploy on every push), then call the generic endpoint as the last CI step:
# .github/workflows/deploy.yml
name: Deploy after tests
on:
push:
branches: [main]
jobs:
test-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: npm test
- name: Trigger DeployKit deployment
run: |
curl -fsS -X POST https://panel.yourdomain.com/api/webhooks/generic \
-H "Content-Type: application/json" \
-H "X-Webhook-Token: ${{ secrets.DEPLOYKIT_WEBHOOK_TOKEN }}" \
-d '{"ref":"${{ github.ref }}","after":"${{ github.sha }}","repository":{"clone_url":"${{ github.server_url }}/${{ github.repository }}.git"}}'You can also call the tRPC API with a user session: log in with auth.login and call application.deploy with the app id. The user needs the operator role on the project, access tokens expire after 15 minutes, and a deploy already in progress returns a conflict.
TOKEN=$(curl -s -X POST https://panel.yourdomain.com/trpc/auth.login \
-H "Content-Type: application/json" \
-d '{"email":"ci@example.com","password":"..."}' \
| jq -r '.result.data.accessToken')
curl -X POST https://panel.yourdomain.com/trpc/application.deploy \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":"your-app-id"}'Webhook Security
- GitHub: HMAC-SHA256 of the exact request bytes, checked against the GitHub App's webhook secret or WEBHOOK_SECRET (either is accepted)
- GitLab: X-Gitlab-Token compared with WEBHOOK_SECRET
- Generic: X-Webhook-Token compared with the app's own secret or WEBHOOK_SECRET; without a valid token the endpoint never reveals whether a repository matched
- All comparisons are constant-time; with no secret configured, deliveries are rejected (fail-closed)
WEBHOOK_SECRET becomes optional once a GitHub App is registered, but keep it for GitLab, the generic endpoint and any hand-made repository webhooks.
Push not deploying? Check the delivery in your Git provider (GitHub: Settings → Webhooks → Recent Deliveries). A 401 means the secret does not match — and because the GitHub signature is computed over the raw body, anything between GitHub and DeployKit that rewrites the request body will also break it. A 200 with "No applications found" means the repository URL or branch configured in DeployKit does not match the push.