Run a blink test on every deploy
Testing once tells you the app was fine once. Wire Blinkof.ai into your CI and every deploy gets the same pass a real user would give it — and the build fails before a new critical reaches anyone.
Why test on every deploy
Vibe coding makes the next change as fast as the first. That's the point — and the risk. The signup that worked last week breaks when you tweak the form; a refactor re-exposes a key; a new page ships without security headers. A one-time audit can't see any of that. The fix isn't more discipline, it's making the check automatic: run it on every deploy, and let a real failure stop the release instead of a user finding it.
Blinkof.ai's API does exactly the free scan you already know — sign up like a customer, throw hostile input at your forms, read your shipped bundles and database rules, walk your money flows, check your privacy promises — and hands CI a score and a count of what's critical. This guide wires it into GitHub Actions; the same three calls work in any CI.
One rule that makes it work: scan the new version, not the old one
Blinkof tests your live URL over the network — it only ever sees whatever is serving there the moment it runs. So the whole thing hinges on timing: scan only once the new build is live. Kick off the scan the instant you push while the deploy is still building, and you've just graded the previous version. There are two timing-correct patterns, and they give you two different results:
- A) A safety net on production — scan after the deploy goes live; a red build means "a regression just shipped — roll back." Catch-and-recover.
- B) A gate before merge — deploy the PR to a verified staging URL, scan that, and block the merge on a new critical, with the verdict posted on the PR. Catch-before-users.
1. Get a token (and verify your domain)
On a Pro or Studio plan, open your dashboard, create an API token under "Blink test on every deploy," and add it to your repo as BLINKOF_TOKEN (Settings → Secrets and variables → Actions). Deep API scans only run against a host you've verified you own — so verify your production (and staging) domain in the dashboard first.
2A. Production safety net (post-deploy)
This keys off your host's deployment event, which reports success only once the new build is actually live — so there's nothing to wait for. Vercel, Cloudflare Pages, Netlify and friends all emit it.
name: Blink test (production)
on:
deployment_status
jobs:
blink:
if: github.event.deployment_status.state == 'success'
runs-on: ubuntu-latest
steps:
- name: Blinkof.ai scan
env:
BLINKOF_TOKEN: ${{ secrets.BLINKOF_TOKEN }}
APP_URL: ${{ github.event.deployment_status.environment_url || 'https://your-app.com' }}
run: |
set -euo pipefail
res=$(curl -sf -X POST https://blinkof.ai/api/v1/scans \
-H "Authorization: Bearer $BLINKOF_TOKEN" -H "Content-Type: application/json" \
-d "{\"url\":\"$APP_URL\"}")
id=$(echo "$res" | jq -r '.id')
echo "Report: https://blinkof.ai/report/$id"
for _ in $(seq 1 60); do
sleep 5
out=$(curl -sf https://blinkof.ai/api/v1/scans/"$id" -H "Authorization: Bearer $BLINKOF_TOKEN")
[ "$(echo "$out" | jq -r '.status')" = "complete" ] && break
done
echo "$out" | jq '{score, grade, counts, new}'
crit=$(echo "$out" | jq -r '.counts.critical')
if [ "$crit" != "0" ]; then
echo "::error::$crit critical(s) live in production — roll back."
exit 1
fi
The deploy already happened, so this is an alarm, not a gate — a red run is your cue to roll back or hotfix.
2B. Gate the merge (pre-merge, on a verified staging URL)
To stop a regression before it ships, deploy the PR to a stable staging domain you've verified, wait until it's serving that commit, scan it, then block the merge and post the result on the PR. (Ephemeral per-deploy preview URLs won't work — a deep scan needs a verified host, and those random subdomains can't be verified. Use one stable staging domain.)
name: Blink test (PR gate)
on:
pull_request:
branches: [main]
permissions:
contents: read
pull-requests: write
jobs:
blink:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy this PR to staging
run: ./deploy-staging.sh # however you ship — e.g. wrangler deploy --env staging
- name: Blink test staging
id: blink
env:
BLINKOF_TOKEN: ${{ secrets.BLINKOF_TOKEN }}
APP_URL: https://staging.your-app.com # must be verified in Blinkof
run: |
set -euo pipefail
# Wait until staging is serving THIS commit before scanning.
# (Expose the build SHA at /version, or swap in your own liveness check.)
for _ in $(seq 1 60); do
[ "$(curl -sf "$APP_URL/version" || true)" = "$GITHUB_SHA" ] && break
sleep 5
done
res=$(curl -sf -X POST https://blinkof.ai/api/v1/scans \
-H "Authorization: Bearer $BLINKOF_TOKEN" -H "Content-Type: application/json" \
-d "{\"url\":\"$APP_URL\"}")
id=$(echo "$res" | jq -r '.id')
for _ in $(seq 1 60); do
sleep 5
out=$(curl -sf https://blinkof.ai/api/v1/scans/"$id" -H "Authorization: Bearer $BLINKOF_TOKEN")
[ "$(echo "$out" | jq -r '.status')" = "complete" ] && break
done
{
echo "score=$(echo "$out" | jq -r '.score')"
echo "crit=$(echo "$out" | jq -r '.counts.critical')"
echo "new=$(echo "$out" | jq -r '.new // 0')"
echo "report=https://blinkof.ai/report/$id"
} >> "$GITHUB_OUTPUT"
- name: Post the result on the PR
if: always()
uses: actions/github-script@v7
env:
SCORE: ${{ steps.blink.outputs.score }}
CRIT: ${{ steps.blink.outputs.crit }}
NEW: ${{ steps.blink.outputs.new }}
REPORT: ${{ steps.blink.outputs.report }}
with:
script: |
const { SCORE, CRIT, NEW, REPORT } = process.env;
const body = `**Blink test** — score **${SCORE}/100** · ${CRIT} critical · ${NEW} new since last scan\n\n[Full report →](${REPORT})`;
const cs = await github.rest.issues.listComments({ ...context.repo, issue_number: context.issue.number });
const mine = cs.data.find(c => c.body.startsWith('**Blink test**'));
if (mine) await github.rest.issues.updateComment({ ...context.repo, comment_id: mine.id, body });
else await github.rest.issues.createComment({ ...context.repo, issue_number: context.issue.number, body });
- name: Block merge on a new critical
run: '[ "${{ steps.blink.outputs.crit }}" = "0" ] || { echo "::error::New criticals — fix before merge."; exit 1; }'
Make this job a required status check (Settings → Branches) and a failing blink test blocks the merge — with the score, the count of what's new, and a link to the full report sitting right on the PR.
3. Tune what fails the build
The example fails on any critical. Pick the bar that fits where you are:
- Strictest — fail on anything new since the last scan: change the check to
new=$(echo "$out" | jq -r '.new // 0'); [ "$new" = "0" ]. Great once you're at a clean baseline. - Balanced — fail on criticals only (the default above). Lets you ship with known low-priority items while blocking the dangerous ones.
- Gentlest — never fail; just print the score and report link so it shows up in the run log. Drop the
exit 1branch.
Every run also updates the app on your dashboard, so you get the score-over-time graph and the "fixed / new since last scan" diff for free — CI just becomes another thing that triggers it.
The API, briefly
Two endpoints, Bearer-authed with your token:
POST /api/v1/scanswith{"url":"…"}→{ id, status, report_url, result_url }. Queues a deep scan.GET /api/v1/scans/:id→{ status, score, grade, counts, fixed, new, report_url }once complete.
It's just HTTP, so the same pattern drops into GitLab CI, CircleCI, a deploy hook, or a one-line cron — anywhere you can run curl.
Whatever you ship with
Vercel, Cloudflare, Netlify, Render, a VPS — Blinkof doesn't care how you deploy, because it tests the live URL. Just keep the one rule that makes any of this meaningful: scan only after the new build is live there, on a domain you've verified.
Try it once by hand first
Paste your URL and see the report you'll be gating on — free, in under a minute.
Run a free blink test →