REST API
Public REST namespace exposed by the plugin. Built on the WordPress REST API stack — register_rest_route, capability-checked, nonce or Basic-Auth backed.
Setting up?
Mint admin API keys under API Keys Settings — Basic Auth consumer key/secret pairs with scope and IP whitelist.

Namespaces
Two namespaces ship:
| Namespace | Used by |
|---|---|
wkwc_wallet/v1 | front-end customer flows (OTP verify, gateway helpers) |
wkwp-wallet/v1 | first-class admin REST (wallets, transactions, analytics) |
Authentication
For customer-facing endpoints (wkwc_wallet/v1)
Use the WordPress login cookie + X-WP-Nonce header. The nonce is rendered into the page by wp_localize_script. No API key needed.
For admin endpoints (wkwp-wallet/v1)
Two options:
| Method | When |
|---|---|
| WordPress capability check | Use the same cookie session as WP admin (Ajax / dashboard widget) |
| Basic Auth with consumer_key / consumer_secret | Use from a server-to-server integration |
Mint an API key
Wallet → Settings → API Keys → Add new.
| Field | Notes |
|---|---|
| Description | human label (e.g. accounting export script) |
| Scope | read / write / read_write |
| Owner user | which WP user the key acts as |
| IP whitelist | optional CSV of IPs |
Submit → consumer_key + consumer_secret shown once. Save them securely; the secret is hashed at rest.
Use Basic Auth
curl -u ck_xxxx:cs_xxxx \
"https://yourstore.com/wp-json/wkwp-wallet/v1/wallets?per_page=20"
The plugin verifies:
- Key is not revoked
- Key has the requested scope
- Caller IP matches whitelist (when set)
- Owner user has the underlying capability for the action
Endpoints
wkwc_wallet/v1 — front-end
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST | /verify_otp/{otp} | logged-in + nonce | Verify transfer OTP, debit sender + credit recipient atomically |
POST | /qr_pay | logged-in + nonce | Settle a QR pay event |
POST | /bnpl_repay | logged-in + nonce | Manual BNPL repayment |
POST /verify_otp/{otp}
Headers: X-WP-Nonce. Body:
{
"recipient_id": 42,
"amount": 250.00,
"note": "Lunch split"
}
Response on success:
{
"success": true,
"transfer_id": 1234,
"balance_after": 750.00
}
Errors: invalid_otp / expired_otp / insufficient_balance / kyc_required / recipient_invalid.
wkwp-wallet/v1 — admin
| Method | Path | Scope |
|---|---|---|
GET | /wallets | read |
GET | /wallets/{user_id} | read |
POST | /wallets/{user_id}/credit | write |
POST | /wallets/{user_id}/debit | write |
GET | /transactions | read |
GET | /transactions/{id} | read |
GET | /withdrawals | read |
POST | /withdrawals/{id}/approve | write |
POST | /withdrawals/{id}/reject | write |
GET | /refund_requests | read |
POST | /refund_requests/{id}/approve | write |
POST | /refund_requests/{id}/reject | write |
GET | /kyc | read |
POST | /kyc/{id}/approve | write |
POST | /kyc/{id}/reject | write |
GET | /analytics/summary | read |
GET | /analytics/chart | read |
GET /wallets
Query params: per_page (default 20, max 100), page, min_balance, max_balance, sort (balance_desc / recent_activity_desc).
Response:
{
"page": 1,
"per_page": 20,
"total": 412,
"items": [
{
"user_id": 42,
"email": "[email protected]",
"balance": 1250.00,
"currency": "INR",
"kyc_status": "approved",
"last_activity_at": "2026-04-29T07:14:23Z"
}
]
}
POST /wallets/{user_id}/credit
Body:
{
"amount": 100,
"note": "Q4 bonus",
"reference": "Q4_BONUS_2026"
}
Response:
{
"balance_after": 1350.00,
"ledger_id": 78421
}
GET /transactions
Query params: user_id, type, from, to, per_page, page, reference.
Each row includes the standard ledger fields: id, user_id, amount, balance_after, currency, type, order_id, reference, note, created_at.
GET /analytics/summary
See Analytics.
GET /analytics/chart
Query: days (1-365), metric. See Analytics.
Error Format
All errors use the standard WP REST error envelope:
{
"code": "kyc_required",
"message": "Sender must have an approved KYC submission to transfer.",
"data": { "status": 403 }
}
Rate Limiting
| Scope | Default |
|---|---|
| Per IP | 60 / minute |
| Per consumer_key | 600 / minute |
| Burst (5-second window) | 30 |
Exceeded → 429 Too Many Requests with Retry-After header. Filter wkwp_rest_rate_limit_* to override.
CORS
Same-origin by default. To expose to a separate front-end domain (headless WC):
add_filter( 'wkwp_rest_cors_origins', function() {
return [ 'https://app.example.com' ];
} );
Webhooks
The plugin doesn't ship a generic outbound webhook engine. Instead, hook into the actions in Filters & Hooks and POST to your destination from your own glue code:
add_action( 'wkwp_wallet_balance_changed', function( $user_id, $amount, $type, $balance_after ) {
wp_remote_post( 'https://hooks.example.com/wallet-event', [
'body' => json_encode( compact( 'user_id', 'amount', 'type', 'balance_after' ) ),
'headers' => [ 'Content-Type' => 'application/json' ],
] );
}, 10, 4 );
OpenAPI / Schema
WP REST emits a schema document at GET /wp-json/wkwp-wallet/v1 (the namespace root). Use it to generate clients with OpenAPI tooling.
Hooks
| Hook | Type | When |
|---|---|---|
wkwp_rest_register_routes | action | append custom routes inside the namespace |
wkwp_rest_pre_authenticate | filter | swap auth provider |
wkwp_rest_response_<endpoint> | filter | mutate response payload |
wkwp_rest_rate_limit_* | filter | override per-tier rate limits |
Test the API
# List wallets
curl -u ck_xxxx:cs_xxxx \
"https://yourstore.com/wp-json/wkwp-wallet/v1/wallets?per_page=5"
# Credit a user
curl -u ck_xxxx:cs_xxxx \
-X POST \
-H "Content-Type: application/json" \
-d '{"amount":100,"note":"API test"}' \
"https://yourstore.com/wp-json/wkwp-wallet/v1/wallets/42/credit"
# Pull analytics
curl -u ck_xxxx:cs_xxxx \
"https://yourstore.com/wp-json/wkwp-wallet/v1/analytics/summary?window=30"
Troubleshooting
| Problem | Fix |
|---|---|
401 invalid_credentials | Key revoked or scope mismatch |
403 forbidden_caller_ip | Caller IP not in whitelist |
429 too_many_requests | Wait Retry-After seconds, raise the rate limit, or use multiple keys |
Permalinks 404 on /wp-json/... | Refresh permalinks: Settings → Permalinks → Save |
