Agent skill
Currents Webhooks Skill
Receive and authenticate Currents HTTP webhooks. Use when setting up a Currents (currents.dev) webhook endpoint for Playwright test runs, checking the secret custom header, debugging 401s, or handling RUN_START, RUN_FINISH, RUN_TIMEOUT and RUN_CANCELED events.
Install this skill
npx skills add hookdeck/webhook-skills --skill currents-webhooks
Currents (currents.dev) reports Playwright test runs. Its HTTP Webhook integration sends a JSON POST when a run starts, finishes, times out or is canceled. Webhooks are not signed. You authenticate them with a secret header that you add to the integration's Headers field; your endpoint compares that header with the value you stored.
The header name is your choice, so the examples read it from CURRENTS_WEBHOOK_HEADER (default Authorization) and the expected value from CURRENTS_WEBHOOK_SECRET.
When to Use This Skill
- How do I receive Currents webhooks?
- How do I authenticate a Currents HTTP webhook? (No signature: use a secret custom header)
- How do I handle
RUN_START,RUN_FINISH,RUN_TIMEOUTorRUN_CANCELEDevents? - Why does my Currents webhook endpoint return 401?
- What is the
x-timestampheader on Currents webhooks?
Verification (core)
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyCurrentsRequest(
headers,
secret = process.env.CURRENTS_WEBHOOK_SECRET,
headerName = process.env.CURRENTS_WEBHOOK_HEADER || 'Authorization'
) {
if (!secret) return false; // never accept when unconfigured
const received = headers[headerName.toLowerCase()]; // Node lowercases header names
return Boolean(received) && safeEqual(received, secret); // WHOLE value, no prefix parsing
}
- Compare the whole header value. If you typed
Bearer abc123into Currents, thenCURRENTS_WEBHOOK_SECRETisBearer abc123. Don't strip or parse the prefix. - Look the header up case-insensitively. Return
401on a missing header or a mismatch. - Read the body raw (
express.raw({ type: '*/*' })) so any content type works, and parse the JSON only after the check passes. - A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, and the only timestamp is unsigned). Use HTTPS and rotate the credential if it leaks.
- Currents also sends an
x-timestampheader with the system's epoch timestamp, which its docs offer to help you prevent replay attacks. It is not signed, so anyone holding the secret header can set a fresh value: treat it as a freshness hint at most, and handle events idempotently.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Types
The payload's event field is one of these (exact strings, upper case):
| Event | Sent When |
|---|---|
RUN_START | A new run starts |
RUN_FINISH | A run finishes |
RUN_TIMEOUT | A run times out |
RUN_CANCELED | A run is canceled |
Runs with several groups (multiple Playwright projects under one ci-build-id) send one request per group, each with its own groupId.
Environment Variables
CURRENTS_WEBHOOK_HEADER=Authorization # header name you added in the integration's Headers
CURRENTS_WEBHOOK_SECRET=your-secret-value # exact, whole header value
Local Development
Currents can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 currents --path /webhooks/currents
Use the printed URL as the webhook URL in Currents.
Resources
- references/overview.md - What Currents webhooks are, events and payload shape
- references/setup.md - Add the HTTP Webhook integration and the secret header
- references/verification.md - Header check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests