Authentication required.
Generate your own API key — no signup, no env vars:
Generate API key
Or call POST https://nobody-needs-this.vercel.app/api/keys with an empty JSON body.
Send your key on every request (except public routes):
Authorization: Bearer YOUR_API_KEY
X-API-Key: YOUR_API_KEY
Revoke: DELETE /api/keys with your key. Admin routes need ADMIN_API_KEY in production.
Base URL: https://nobody-needs-this.vercel.app
All requests use Content-Type: application/json unless noted.
Endpoints
GET
Health check
/api/health
Public
Returns service status. No authentication required.
Example response
{ "status": "healthy", "service": "payment-simulator", "environment": "production", "auth_required": true, "registration_enabled": true, "api_key_store": "redis" }
POST
Create API key
/api/keys
Public
Generate a personal API key (shown once). No auth required when ENABLE_API_KEY_REGISTRATION is true. Rate-limited per IP.
Copy curl -X POST "https://nobody-needs-this.vercel.app/api/keys" \
-H "Content-Type: application/json" \
-d '{}'
Example response
{ "success": true, "api_key": "ps_live_…", "message": "Save this key now…" }
DELETE
Revoke API key
/api/keys
API key required
Revokes the API key sent on this request. Bootstrap/env keys cannot be revoked via this endpoint.
Example response
{ "success": true, "message": "API key revoked" }
POST
M-Pesa STK Push
/api/payments/mpesa/stk-push
API key required
Initiate a fake M-Pesa STK push. With auto_complete (default true), a callback is simulated after ~2 seconds.
Field Notes
phone_number
yes
254XXXXXXXXX
amount
yes
Number
account_reference
no
Default: TEST
description
no
Default: Payment
callback_url
no
Webhook URL when payment completes
auto_complete
no
true = simulate callback after ~2s (default true)
force_success
no
true / false / omit for random (~95% success)
Copy curl -X POST "https://nobody-needs-this.vercel.app/api/payments/mpesa/stk-push" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone_number":"254712345678","amount":1000,"account_reference":"ORDER-123","description":"Payment","callback_url":"https://your-app.com/webhooks/mpesa","auto_complete":true,"force_success":true}'
POST
M-Pesa callback (manual)
/api/payments/mpesa/callback
API key required
Manually trigger the M-Pesa STK callback for a transaction. Use when auto_complete is false.
Field Notes
transaction_id
yes
From stk-push response
force_success
no
true = success (0), false = cancelled (1032)
Copy curl -X POST "https://nobody-needs-this.vercel.app/api/payments/mpesa/callback" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"transaction_id":"MPXABC123","force_success":true}'
POST
Bank transfer
/api/payments/bank-transfer
API key required
Initiate a fake bank transfer. Auto-completes after ~3 seconds when auto_complete is true.
Field Notes
account_number
yes
Beneficiary account
bank_code
yes
Bank identifier
amount
yes
Number
reference
no
Default: TEST
narration
no
Default: Payment
callback_url
no
Webhook URL on completion
auto_complete
no
Default true
force_success
no
true / false / random
Copy curl -X POST "https://nobody-needs-this.vercel.app/api/payments/bank-transfer" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"account_number":"1234567890","bank_code":"01","amount":5000,"reference":"INV-123","narration":"Supplier payment","callback_url":"https://your-app.com/webhooks/bank","auto_complete":true,"force_success":true}'
POST
Bank transfer complete (manual)
/api/payments/bank-transfer/complete
API key required
Manually complete a bank transfer when auto_complete is false.
Field Notes
transaction_id
yes
From bank-transfer response
force_success
no
true = completed, false = failed
Copy curl -X POST "https://nobody-needs-this.vercel.app/api/payments/bank-transfer/complete" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"transaction_id":"BNKABC123","force_success":true}'
POST
PesaLink name inquiry
/api/payments/pesalink/name-inquiry
API key required
Resolve a beneficiary before sending. STA: bank_code + account_number. STP: phone_number. Deterministic fake names (same inputs always return the same name). Sentinels: account ending 00 → 14 not found (HTTP 404); phone with even last digit → not linked (HTTP 404).
Field Notes
bank_code
STA
Illustrative sort code, e.g. 68 = Equity
account_number
STA
Ends in 00 → not found
phone_number
STP
Even last digit → not linked
Copy curl -X POST "https://nobody-needs-this.vercel.app/api/payments/pesalink/name-inquiry" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"bank_code":"68","account_number":"0123456789"}'
Example response
{ "success": true, "found": true, "response_code": "00", "bank_code": "68", "bank_name": "Equity Bank Kenya", "account_number": "0123456789", "account_name": "WANJIKU MWANGI" }
POST
PesaLink send
/api/payments/pesalink/send
API key required
Initiate a fake IPSL credit transfer. type=account (STA) needs bank_code+account_number; type=phone (STP) needs phone_number. Amounts outside KES 10–999,999 return 422 with code 61. Retries with the same Idempotency-Key header (or idempotency_key / a non-default reference) return the original PSL… id; a different payload with that key returns 409 / 94. auto_complete (default true) fires a callback after ~2s — unreliable on Vercel; use POST /complete there. force_outcome: success | insufficient_funds | issuer_unavailable | invalid_account | duplicate.
Field Notes
type
no
account (STA, default) or phone (STP)
bank_code
STA
Required for type=account
account_number
STA
Required for type=account
phone_number
STP
Required for type=phone; odd last digit = linked
amount
yes
KES 10–999,999 or 422 / code 61
reference
no
Default: TEST (not used for dedupe). Any other value keys retries
idempotency_key
no
Or Idempotency-Key header. Same key + same payload → original PSL…; mismatch → 409 / 94
narration
no
Default: Payment
callback_url
no
Webhook URL on completion
auto_complete
no
Default true (~2s). On Vercel use /complete instead
force_outcome
no
success | insufficient_funds | issuer_unavailable | invalid_account | duplicate
Copy curl -X POST "https://nobody-needs-this.vercel.app/api/payments/pesalink/send" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"account","bank_code":"68","account_number":"0123456789","amount":500,"reference":"INV-123","narration":"Payment","idempotency_key":"order-123-send","callback_url":"https://your-app.com/webhooks/pesalink","auto_complete":true,"force_outcome":"success"}'
POST
PesaLink complete (manual)
/api/payments/pesalink/complete
API key required
Manually complete a PesaLink transfer. This is the reliable path on Vercel — background threads die after the HTTP response, so do not depend on auto_complete there. Returns ISO 8583-style codes (00 success + rrn, 51, 91 reversed, 14, 94).
Field Notes
transaction_id
yes
From /send response
force_outcome
no
Same values as /send
Copy curl -X POST "https://nobody-needs-this.vercel.app/api/payments/pesalink/complete" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"transaction_id":"PSLABC123","force_outcome":"success"}'
GET
PesaLink banks
/api/payments/pesalink/banks
API key required
Illustrative Kenyan sort codes used by this fake switch. Not the official IPSL participant list.
Example response
{ "success": true, "note": "Illustrative sort codes, not the official IPSL participant list.", "banks": [{ "code": "01", "name": "KCB Bank Kenya" }, { "code": "68", "name": "Equity Bank Kenya" }] }
GET
Transaction status
/api/payments/:transaction_id
API key required
Get the current state of a single transaction.
Field Notes
transaction_id
yes
Path parameter, e.g. MPX…, BNK…, or PSL…
GET
List transactions
/api/payments
API key required
List in-memory transactions. Optional query filters.
Field Notes
status
query
pending | processing | completed | failed | cancelled
method
query
mpesa | bank_transfer | pesalink
POST
Reset all transactions
/api/payments/reset
Admin key required in production
Clears all stored transactions. In production requires ADMIN_API_KEY.
Copy curl -X POST "https://nobody-needs-this.vercel.app/api/payments/reset" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
Webhooks
Include callback_url when starting a payment. When the flow completes, the simulator POSTs JSON to that URL (same shape as real providers).
For local testing, run ruby webhook_receiver.rb on port 4567 and use:
http://localhost:4567/webhooks/mpesa (local receiver)
…/webhooks/bank
…/webhooks/pesalink
On serverless hosts (e.g. Vercel), prefer auto_complete: false and call the manual callback endpoints, or use a public inbox like webhook.site.
M-Pesa result codes
Code Meaning
0Success
1032User cancelled
1037Timeout (no PIN)
2001Wrong PIN
1Insufficient balance
PesaLink response codes
ISO 8583-shaped, fake. force_outcome on /send and /complete: success, insufficient_funds, issuer_unavailable, invalid_account, duplicate.
Code Meaning
00Approved (sets an rrn)
14Invalid / not found account (or phone not linked)
51Insufficient funds
61Exceeds PesaLink amount limit (HTTP 422 on /send)
91Receiving bank timeout — reversed: true
94Duplicate transmission
Payment Simulator — built for testing only.
See the project README for Rails integration examples.