How We Built a Safe GitHub Bounty Lifecycle for MyZubster
MyZubster is evolving into a distributed ecosystem of repositories, services, automation, hardware projects, AI components, and contributor workflows.
As the number of repositories and contributors increased, one problem became increasingly important:
How do we automate bounty workflows without accidentally treating a GitHub event as proof of payment, verification, or settlement?
We recently completed an important part of that architecture: a real-time GitHub bounty lifecycle system.
And we tested it end-to-end.
The lifecycle
We use an explicit bounty lifecycle instead of assuming that an issue, pull request, or merge means a bounty has been completed.
The lifecycle is roughly:
text
PROPOSED
↓
VALIDATED
↓
APPROVED
↓
FUNDED
↓
ACTIVE
↓
SUBMITTED
↓
UNDER_REVIEW
↓
VERIFIED
↓
REWARD_RECORDED
↓
SETTLEMENT_PENDING
↓
SETTLED
The important part is that GitHub automation only controls a limited part of this flow.
Today, GitHub can automatically move a bounty through:
APPROVED
↓ assignment
ACTIVE
↓ linked PR
SUBMITTED
↓ review
UNDER_REVIEW
And then automation stops.
GitHub Webhooks Across the Ecosystem
We configured repository webhooks across 17 first-party MyZubster repositories.
The subscribed events are:
issues
pull_request
pull_request_review
The central endpoint is:
POST /api/github-bounties/webhook
The backend is Node.js / Express and validates GitHub webhook signatures using:
X-Hub-Signature-256
with an HMAC-SHA256 secret.
Unsigned requests are rejected.
For example:
POST /api/github-bounties/webhook
→ HTTP 401
while valid GitHub webhook deliveries receive a normal application response.
A Useful Production Bug: PM2 Had a Stale Secret
One of the most interesting parts of the deployment was a real production debugging problem.
GitHub was delivering webhook events correctly, but every delivery returned:
401 Unauthorized
Cloudflare was healthy.
The public API was healthy.
The webhook route was healthy.
GitHub deliveries were reaching the server.
But signature verification failed.
We eventually compared the webhook secret loaded from .env with the value injected into the running PM2 process.
The result:
dotenvSecret: SET
pm2InjectedSecret: SET
sameSecret: false
That was the bug.
PM2 was still running with an older environment variable.
After restarting the application with the updated environment:
pm2 restart myzubster --update-env
GitHub's webhook ping changed to:
code: 200
status: active
message: OK
A small configuration mismatch had effectively disabled the entire real-time automation layer.
This was also a useful reminder:
Secrets are not only a storage problem. They are also a process lifecycle problem.
End-to-End Test
Once the webhook secret was synchronized, we ran a real GitHub lifecycle test.
We created a temporary bounty with:
type:bounty
status:approved
reward:none
evidence:required
Using reward:none was intentional: the test should exercise lifecycle automation without involving any financial state.
Step 1 — Assignment
The issue was assigned to a contributor.
GitHub sent:
issues.assigned
The backend changed:
status:approved
to:
status:active
Success.
Step 2 — Pull Request
We created a non-draft pull request containing:
Fixes #523
The webhook processor detected the linked bounty and changed:
status:active
to:
status:submitted
Success.
Step 3 — Review
A pull request review was submitted.
The bounty changed from:
status:submitted
to:
status:review
which is normalized by the backend as:
under_review
Success.
The complete real-time path was therefore validated:
APPROVED
↓
ACTIVE
↓
SUBMITTED
↓
UNDER_REVIEW
What Does NOT Happen Automatically
This is probably the most important design decision.
A GitHub event does not automatically produce:
status:verified
status:reward-recorded
settlement:*
A merge does not automatically mean:
bounty verified
and it definitely does not mean:
payment completed
The system does not treat repository activity as financial evidence.
MYZ Is an Internal Platform Ledger Unit
Another distinction we make explicitly:
MYZ is currently an internal platform accounting/reward unit.
We do not describe current MYZ rewards as blockchain payouts.
Likewise, external assets such as XMR or other tokens require independent settlement verification before they can be considered paid or settled.
This means:
GitHub merge
≠ bounty verification
bounty verification
≠ reward recording
reward recording
≠ external settlement
Keeping those concepts separate makes the automation much safer.
GitHub as Source of Workflow Truth
Lifecycle labels are intentionally explicit.
Examples include:
status:proposed
status:validated
status:approved
status:funded
status:active
status:submitted
status:review
status:verified
status:reward-recorded
Reward declarations are also explicit:
reward:myz
reward:xmr
reward:token
reward:none
Review policies can include:
review:manual
review:multi
and sensitive work can be marked:
sensitivity:high
This allows automation to reason from explicit repository state instead of guessing.
Next Step: Safe Auto-Merge
The next evolution is already in the MyZubster roadmap:
safe auto-merge for eligible bounty pull requests.
But this will not be a generic "bot approves and merges everything" system.
An eligible PR should require conditions such as:
CI green
required checks complete
minimum approved reviews satisfied
no requested changes
no blocking labels
no sensitivity:high
no review:manual
For multi-review bounties:
review:multi
we plan to require at least:
2 independent approvals
before automatic merge becomes possible.
Branch protection and repository rulesets should provide another enforcement layer.
Merge Still Does Not Mean Payment
Even after safe auto-merge is implemented, the architecture will keep the same hard boundary:
AUTO MERGE
↓
code integrated
↓
STOP
It must not automatically perform:
VERIFIED
REWARD_RECORDED
SETTLED
Those remain separate evidence and financial operations.
In other words:
Merge != verification != reward != settlement.
Why This Matters
Automation is easy when everything is treated as the same event.
The dangerous version looks like this:
PR merged
↓
bounty complete
↓
credit reward
↓
mark paid
It is convenient.
It is also very difficult to audit safely.
We prefer:
GitHub activity
↓
workflow state
↓
review / evidence
↓
verification
↓
reward accounting
↓
independent settlement verification
It is more explicit, but every transition has a meaning.
Current Status
At this point we have validated:
GitHub webhook connectivity through Cloudflare
HMAC webhook authentication
repository webhook deployment across 17 first-party repositories
GitHub issue assignment → active
linked pull request → submitted
pull request review → under_review
protection against automatic verification
protection against automatic reward recording
protection against automatic settlement
The temporary E2E test artifacts were then closed and cleaned up without merging the test PR.
What's Next
The next milestones for this part of MyZubster are:
1. Safe CI + review gated auto-merge
2. Unified bounty board
3. Real multi-review / Super Bounty backend
4. Explicit MYZ internal reward ledger integration
5. Signed public history / evidence snapshots
6. Independent settlement verification
The goal is not maximum automation.
The goal is automation with explicit trust boundaries.
If you're building GitHub-based bounty systems, contributor economies, autonomous agents, or open-source reward infrastructure, I'd be very interested in comparing architectures.
How much of the workflow would you automate before requiring a human or independent verification step?
#opensource #github #automation #nodejs
Top comments (0)