Webhook Receiver
Instead of polling, receive a push notification when your job completes. This minimal Express server handles job.completed, job.failed, and job.claimed events.
Setup
cd examples/webhook-receiver npm install export AGENTQA_WEBHOOK_SECRET="whsec_..." # from Settings → Webhooks node server.js # → Listening on port 3001
Testing locally
# Expose to the internet with ngrok ngrok http 3001 # Copy the ngrok URL: # e.g. https://abc123.ngrok.io/webhook # Paste into: BetaWindow Settings → Webhooks → Add endpoint
Event payload — job.completed
{
"type": "job.completed",
"data": {
"id": "job_abc123",
"status": "complete",
"submitted_url": "https://your-app.vercel.app",
"tier": "standard",
"rating": 4,
"summary": "Signup and checkout work. Found one medium bug.",
"bugs": [
{
"severity": "medium",
"title": "Email field accepts invalid addresses",
"steps": "1. Go /signup\n2. Enter 'user@'\n3. Submit",
"expected": "Validation error",
"actual": "Server 500"
}
],
"network_log_url": "/report/job_abc123/network",
"console_log_url": "/report/job_abc123/console",
"completed_at": "2025-04-15T14:32:10Z"
}
}Signature verification
Every request includes X-BetaWindow-Signature: sha256=<hmac>. The server verifies using crypto.timingSafeEqual to prevent timing attacks.
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody) // express.raw() required
.digest('hex')
// timingSafeEqual prevents timing attacks
crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(received, 'hex'),
)Common integrations
case 'job.completed': {
const job = event.data
// Slack DM
await slack.chat.postMessage({
channel: '#qa-alerts',
text: `BetaWindow: ${job.bugs.length} bug(s) in ${job.submitted_url}`,
})
// Jira ticket per bug
for (const bug of job.bugs) {
await jira.issues.createIssue({
summary: bug.title,
description: bug.steps,
priority: { name: bug.severity },
})
}
// GitHub commit status
await octokit.repos.createCommitStatus({
state: job.bugs.length === 0 ? 'success' : 'failure',
description: `BetaWindow: ${job.rating}/5 — ${job.summary}`,
})
}Next steps
- Node.js quickstart — polling-based alternative
- TypeScript pipeline — fully typed client
- Sample report