Approve the prod deploy from your phone.
The pipeline pauses, asks on WhatsApp, and continues only on
your tap. One step in your GitHub Actions workflow sends the question with
ship it / abort buttons (POST /v1/ask under the
hood) and blocks until you answer; the deploy step runs only when the answer
is the first button. No reply means no deploy — silence
never ships. And when the job finishes, a one-line ping
(POST /v1/notify) tells you how it went.
The gate: ship it or abort
Two steps: the gate asks and waits (up to 90 seconds, the
server maximum), the deploy runs behind an if on the gate's
button-id output. That's the whole wiring — the action is
grzgrzgrz3/pingwa-action, your key sits in a repo secret.
name: deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Ask before shipping
id: gate
uses: grzgrzgrz3/pingwa-action@v1
with:
api-key: ${{ secrets.PINGWA_KEY }}
mode: ask
message: "Deploy ${{ github.repository }} @ ${{ github.sha }} to prod?"
buttons: "ship it,abort"
- name: Deploy
if: steps.gate.outputs.button-id == 'b0'
run: ./deploy.sh
The fail-safe reading: if you don't answer within the 90
seconds, /v1/ask returns HTTP 408 and the gate
step fails — the deploy step never runs. Tapping
abort keeps the gate green with button-id=b1
and the deploy is simply skipped. button-id is positional:
b0 is the first label you passed, b1 the second —
only an explicit b0 ships. The question was still delivered to
your phone either way; a late tap is retrievable afterwards via
GET /v1/messages/<id>/reply.
If the ask lands outside your 24h reply window (likely, for
deploys that arrive out of the blue), the buttons arrive as a numbered list
instead of tap buttons — replying 1 (or the button's text,
ship it) works the same and still maps to b0.
PINGWA_KEY repo secret.The done-ping: how did it go?
Same action, mode: notify (the default). With
if: always() the ping fires on success and failure, and
job.status puts the verdict in the message — the deploy you
approved from the couch reports back to the couch.
- name: Ping when done
if: always()
uses: grzgrzgrz3/pingwa-action@v1
with:
api-key: ${{ secrets.PINGWA_KEY }}
message: "${{ github.workflow }} on ${{ github.repository }}: ${{ job.status }}"
Not on GitHub? Two curls
GitLab CI, Jenkins, Woodpecker, a bare deploy script — the
gate is the same /v1/ask call with a jq check on
.reply.button_id. The GitHub action is exactly this curl inside;
there's nothing platform-specific about the pattern.
reply=$(curl -sS --max-time 95 -X POST https://pingwa.dev/v1/ask \
-H "Authorization: Bearer $PINGWA_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "Deploy to prod?", "buttons": ["ship it","abort"], "timeout": 90}')
[ "$(printf '%s' "$reply" | jq -r '.reply.button_id')" = "b0" ] || exit 1
./deploy.sh
curl -sS -X POST https://pingwa.dev/v1/notify \
-H "Authorization: Bearer $PINGWA_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "deploy: done"}'
Same fail-safe shape: the gate compares against
b0, so anything else fails it — a 408 or error body makes
jq print null, an abort tap yields b1,
and either way exit 1 stops the script before the deploy line.
One difference from the GitHub variant: that exit 1 also skips
the final notify (a bare script has no if: always()) — move the
notify before the exit, or trap it, if you want failure pings
too.
FAQ
GitHub Actions manual approval without environments?
Honest answer: GitHub's protected environments with required reviewers are the right tool for org-grade compliance — audit trails, review groups, policy. This recipe is the solo-dev phone tap: no environment configuration, no plan requirement, and the approval reaches you where you already are — on WhatsApp, away from the laptop. If you're one person shipping your own project, a tap beats opening github.com on mobile to hunt for the review button.
Will the job hang while it waits?
At most 90 seconds — that's the server maximum for
/v1/ask's long-poll, after which the gate step fails and the
job ends without deploying. The action can't wait longer than that, so a
forgotten question costs you a couple of runner minutes, not hours. Belt
and braces: set a job-level timeout-minutes anyway, as you
would for any job.
Can I really approve a deployment from my phone?
Yes — that's § 1. The workflow sends the question with buttons to your
WhatsApp and genuinely blocks on the gate step; your tap is the approval.
Tap ship it and the deploy step runs, tap
abort or ignore it and nothing ships. Get a key by sending
join once, add it as the PINGWA_KEY secret,
paste the workflow.
Is the action safe to put in my pipeline?
It's a composite action: one bash step, curl +
jq only (both preinstalled on GitHub runners), zero
third-party dependencies, ~40 lines — read
action.yml
yourself, it fits on a screen. If your supply-chain policy is strict, pin
it by full commit SHA instead of @v1.
Scan the code and get instant access. It opens WhatsApp with “join” ready to send, and your API key comes straight back.
Deploy gates are exactly the messages you want rare and deliberate — the free plan's 30 paid messages a month cover a healthy release cadence, and your button-tap replies inside the 24h window are free.