gitpulse-tracker
v1.3.0
Published
Lightweight webhook handler for GitPulse — track real developer contribution quality across any Git project
Maintainers
Readme
gitpulse-tracker
Lightweight webhook bridge for GitPulse — tracks real developer contribution quality across any Git project.
Setup in 3 steps — no code to write
Step 1 — Install
npm install gitpulse-trackerStep 2 — Run the init command
npx gitpulse-tracker initThis auto-detects your framework (Laravel, Next.js App Router, Pages Router, Express, Fastify) and creates the webhook route file for you automatically. You write zero code.
Step 3 — Fill in your .env file
GITPULSE_API_KEY=your_api_key_here
GITPULSE_PROJECT_ID=your_project_id_here
GITPULSE_WEBHOOK_SECRET=choose_any_random_secret
GITPULSE_SERVER_URL=https://app.gitpulse.comGet the API key and project ID from your GitPulse dashboard → Settings → API. The webhook secret is any string you choose — you'll paste the same value into GitHub/GitLab below.
Never commit your
.envfile. Add it to.gitignore.
Step 4 — Point GitHub / GitLab at your app
The init command prints your exact webhook URL. Use it here:
GitHub: repo → Settings → Webhooks → Add webhook
- Payload URL:
https://yourapp.com/api/gitpulse - Content type:
application/json - Secret: same value as
GITPULSE_WEBHOOK_SECRET - Events: Send me everything (or at minimum: Push, Pull requests, Pull request reviews)
GitLab: project → Settings → Webhooks → Add new webhook
- URL:
https://yourapp.com/api/gitpulse - Secret token: same value as
GITPULSE_WEBHOOK_SECRET - Trigger: Push events, Merge request events, Comments
That's it. GitPulse starts tracking contributions on the next push.
What init creates
| Framework detected | Files created | Webhook path |
| -------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------- |
| Laravel | config/gitpulse.php + app/Http/Controllers/GitPulseWebhookController.php + route in routes/api.php | /api/gitpulse/webhook |
| Next.js App Router | app/api/gitpulse/route.js | /api/gitpulse |
| Next.js Pages Router | pages/api/gitpulse-webhook.js | /api/gitpulse-webhook |
| Express | gitpulse-webhook.js (router) | /webhooks/gitpulse |
| Fastify | gitpulse-webhook.js (plugin) | /webhooks/gitpulse |
Detection is automatic — it looks for artisan (Laravel), next in package.json (Next.js), etc. Just run the command from your project root.
For Express and Fastify, init also prints the one-line import to add to your app.js.
If you have a .env.example file, the GitPulse env vars are automatically appended to it.
How it works under the hood
Every time a developer pushes or opens a PR, GitHub/GitLab fires a webhook to your app. The route created by init:
- Verifies the webhook signature (HMAC-SHA256) — only legitimate payloads pass
- Parses the raw payload into a normalized event
- Forwards it to your GitPulse server where it gets scored and stored
No database. No config files. Zero runtime dependencies.
Configuration options (advanced)
All options come from environment variables. If you need to override in code:
import { createGitPulseHandler } from "gitpulse-tracker";
export const POST = createGitPulseHandler({
apiKey: process.env.GITPULSE_API_KEY, // required
projectId: process.env.GITPULSE_PROJECT_ID, // required
serverUrl: process.env.GITPULSE_SERVER_URL, // required — no trailing slash
webhookSecret: process.env.GITPULSE_WEBHOOK_SECRET, // strongly recommended
provider: "github", // optional — auto-detected from request headers
timeout: 10000, // optional — outbound request timeout in ms (default: 10000)
});Low-level API
For fully custom setups, the underlying primitives are exported.
Send events manually
import { GitPulseClient } from "gitpulse-tracker";
const client = new GitPulseClient({
apiKey: process.env.GITPULSE_API_KEY,
projectId: process.env.GITPULSE_PROJECT_ID,
serverUrl: process.env.GITPULSE_SERVER_URL,
});
await client.sendEvent(events, rawPayload);Parse payloads
import { parseGitHubEvent, parseGitLabEvent } from "gitpulse-tracker";
// Returns array of events, or null if the event type is not tracked
const events = parseGitHubEvent(req.headers["x-github-event"], payload);
const events = parseGitLabEvent(req.headers["x-gitlab-event"], payload);Verify signatures
import { verifyGitHubSignature, verifyGitLabToken } from "gitpulse-tracker";
// Both throw { statusCode: 401 } if verification fails
verifyGitHubSignature(
rawBodyString,
webhookSecret,
req.headers["x-hub-signature-256"],
);
verifyGitLabToken(webhookSecret, req.headers["x-gitlab-token"]);Supported events
| Provider | Event | Tracked as |
| -------- | ---------------------------------------------- | -------------- |
| GitHub | push | push |
| GitHub | pull_request opened / reopened / synchronize | pull_request |
| GitHub | pull_request closed + merged | merge |
| GitHub | pull_request_review submitted | review |
| GitHub | issue_comment on a PR | comment |
| GitLab | Push Hook | push |
| GitLab | Tag Push Hook | push |
| GitLab | Merge Request Hook open / reopen / update | pull_request |
| GitLab | Merge Request Hook merge | merge |
| GitLab | Note Hook on a merge request | review |
| GitLab | Note Hook on an issue | comment |
All other event types are silently ignored — your endpoint returns { ok: true, skipped: true }.
Troubleshooting
init says "could not detect framework"
- Run the command from the root of your project (where
package.jsonorartisanlives). - For Laravel, make sure both
artisanandapp/Httpexist in the root — standard for any Laravel project.
Laravel: webhook returns 419 (CSRF token mismatch)
- The webhook URL must be excluded from CSRF protection. The
initcommand prints the exact snippet to add. In Laravel 11+ add it tobootstrap/app.php:
In older Laravel, add->withMiddleware(function (Middleware $middleware) { $middleware->validateCsrfTokens(except: ['api/gitpulse/webhook']); })'api/gitpulse/webhook'to the$exceptarray inapp/Http/Middleware/VerifyCsrfToken.php.
Signature verification fails (401)
- Next.js Pages Router: the generated file includes
export const config = { api: { bodyParser: false } }— do not remove it. - Express: make sure
express.json()is not applied before the webhook route (the generated file includes a comment about this). - Check that
GITPULSE_WEBHOOK_SECRETin.envmatches the secret entered in GitHub/GitLab exactly.
Events are skipped
- GitHub: enable "Pull requests" and "Pull request reviews" in your webhook settings — push-only misses PRs.
- GitLab: enable "Merge request events" and "Comments" in addition to push events.
GitPulseError: HTTP 429
- Your project hit its monthly event limit. Upgrade your plan in the GitPulse dashboard.
Security
- Signatures are verified with HMAC-SHA256 using
crypto.timingSafeEqualto prevent timing attacks. GITPULSE_WEBHOOK_SECRETis never forwarded to the GitPulse server or written to logs.- Always use HTTPS for both your app webhook URL and
GITPULSE_SERVER_URL.
License
MIT
