API Reference — v1
Build with deVIP
Accept payments, authenticate users, and access wallet data — all through a single, well-documented API designed for Nigerian businesses.
OAuth 2.0
Sign in with deVIP — let users auth with their existing account
Wallet Debit
Instantly charge a user's deVIP wallet at checkout
Bank Transfer
Generate unique virtual accounts for bank transfer payments
Webhooks
Receive real-time events when payments succeed
Overview
The deVIP API lets third-party applications integrate deVIP payments and authentication. It supports the OAuth 2.0 Authorization Code flow for user login, direct wallet debit at checkout, and virtual bank account generation for bank transfers.
All endpoints
GET
/oauth/userinfoFetch authenticated user profile
POST
/oauth/tokenExchange auth code for access token
GET
/wallet/balanceGet user's wallet balance
POST
/wallet/debitCharge user's wallet
POST
/transfers/generateGenerate virtual bank account
GET
/transfers/verify/{reference}Check transfer payment status
POST
/transfers/simulate-pay/{reference}Simulate bank transfer (sandbox only)
Getting access: The deVIP API is available to verified business partners. Email dev@devip.me to request your
client_id, client_secret, and webhook_secret.Authentication
Server-to-server API requests authenticate using your Client ID and Client Secret as a Bearer token. The OAuth user token is used separately to call
/oauth/userinfo.http
X-Devip-Client-Id: YOUR_CLIENT_ID
Authorization: Bearer YOUR_CLIENT_SECRET
Accept: application/json
Content-Type: application/jsonKeep it server-side. Never expose your
client_secret in browser JavaScript, mobile apps, or public repositories. All API calls must originate from your backend server.OAuth 2.0 — Sign in with deVIP
Allow your users to authenticate using their deVIP account via the standard OAuth 2.0 Authorization Code flow. This eliminates separate registration for apps that integrate with deVIP.
Protocol Flow
1Your app redirects the user to the deVIP authorization page
2User logs in to deVIP and approves your requested scopes
3deVIP redirects back to your callback URL with a one-time code
4Your backend exchanges the code for an access_token
5Use the access_token to fetch user profile and wallet data
Step 1 — Redirect the User
Redirect the user's browser to the deVIP authorization endpoint.
url
GET https://devip.me/oauth/authorize
?client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/auth/callback
&scope=profile email phone
&state=RANDOM_CSRF_TOKENParameter
Type
Req.
Description
client_idstringYes
Your registered deVIP client ID
redirect_uristringYes
Callback URL. Must match your registered redirect URIs exactly.
scopestringNo
Space-separated scopes: profile, email, phone, wallet. Defaults to profile email.
statestringNo
A random string to prevent CSRF attacks. Verify it matches on callback.
Step 2 — Handle the Callback
After the user approves, they're redirected to your callback URL with a temporary
code. It expires in 10 minutes and is single-use.url
GET https://yourapp.com/auth/callback?code=TEMP_AUTH_CODE&state=RANDOM_CSRF_TOKENStep 3 — Exchange Code for Token
On your server, POST to exchange the code for an access token.
http
POST https://api.devip.me/api/v1/oauth/token
Content-Type: application/json
{
"grant_type": "authorization_code",
"code": "TEMP_AUTH_CODE",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"redirect_uri": "https://yourapp.com/auth/callback"
}json — 200 OK
{
"access_token": "devip_at_xyz789...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "profile email phone"
}Step 4 — Fetch User Profile
http
GET https://api.devip.me/api/v1/oauth/userinfo
Authorization: Bearer devip_at_xyz789...json — 200 OK
{
"sub": "DEVIP-12345",
"name": "John Doe",
"email": "john@example.com",
"phone": "08012345678",
"avatar": "https://api.devip.me/avatars/john.png",
"wallet": {
"balance": 25000.00,
"currency": "NGN"
}
}Available Scopes
profileUser's name and avataremailUser's email addressphoneUser's phone numberwalletNGN wallet balance — requires partner approvalCode Examples
javascript (Node.js)
// Express callback handler
app.get('/auth/callback', async (req, res) => {
const { code, state } = req.query;
// Verify state to prevent CSRF
if (state !== req.session.oauthState) {
return res.status(403).send('Invalid state');
}
const response = await fetch('https://api.devip.me/api/v1/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
code,
client_id: process.env.DEVIP_CLIENT_ID,
client_secret: process.env.DEVIP_CLIENT_SECRET,
redirect_uri: 'https://yourapp.com/auth/callback',
}),
});
const { access_token } = await response.json();
// Fetch user info
const profileRes = await fetch('https://api.devip.me/api/v1/oauth/userinfo', {
headers: { Authorization: `Bearer ${access_token}` },
});
const profile = await profileRes.json();
// Log the user in to your app
req.session.user = profile;
res.redirect('/dashboard');
});python (Django / Flask)
import requests
def oauth_callback(request):
code = request.GET.get('code')
state = request.GET.get('state')
# Verify state
if state != request.session.get('oauth_state'):
return HttpResponse('Invalid state', status=403)
# Exchange code for token
token_res = requests.post('https://api.devip.me/api/v1/oauth/token', json={
'grant_type': 'authorization_code',
'code': code,
'client_id': settings.DEVIP_CLIENT_ID,
'client_secret': settings.DEVIP_CLIENT_SECRET,
'redirect_uri': 'https://yourapp.com/auth/callback',
})
access_token = token_res.json()['access_token']
# Get user profile
profile_res = requests.get('https://api.devip.me/api/v1/oauth/userinfo',
headers={'Authorization': f'Bearer {access_token}'})
profile = profile_res.json()
# Create or update user in your DB
user, _ = User.objects.get_or_create(email=profile['email'])
login(request, user)
return redirect('/dashboard')Get Wallet Balance
Check a user's NGN wallet balance before initiating checkout. Requires the
wallet scope.http
GET https://api.devip.me/api/v1/wallet/balance?account_id=DEVIP-12345
X-Devip-Client-Id: YOUR_CLIENT_ID
Authorization: Bearer YOUR_CLIENT_SECRETParameter
Type
Req.
Description
account_idstringYes
The user's deVIP account ID (from the sub field in userinfo)
json — 200 OK
{
"success": true,
"data": {
"balance": 25000.00,
"currency": "NGN"
}
}Debit Wallet
Instantly charge a user's deVIP wallet during checkout. The user must have previously authorized the
wallet scope.Use a unique
reference per transaction. Duplicate references are rejected to prevent double charges.http
POST https://api.devip.me/api/v1/wallet/debit
X-Devip-Client-Id: YOUR_CLIENT_ID
Authorization: Bearer YOUR_CLIENT_SECRET
Content-Type: application/json
{
"account_id": "DEVIP-12345",
"amount": 18000.00,
"reference": "ORDER-REF-abc123"
}Parameter
Type
Req.
Description
account_idstringYes
The user's deVIP account ID
amountnumberYes
Amount to charge in NGN (e.g. 18000.00)
referencestringYes
Your unique order reference. Used for idempotency.
json — 200 OK
{
"success": true,
"message": "Wallet debited successfully.",
"data": {
"reference": "ORDER-REF-abc123",
"amount": 18000.00,
"previous_balance": 25000.00,
"new_balance": 7000.00
}
}javascript (Node.js)
const response = await fetch('https://api.devip.me/api/v1/wallet/debit', {
method: 'POST',
headers: {
'X-Devip-Client-Id': process.env.DEVIP_CLIENT_ID,
'Authorization': `Bearer ${process.env.DEVIP_CLIENT_SECRET}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
account_id: 'DEVIP-12345',
amount: 18000.00,
reference: `ORDER-${Date.now()}`,
}),
});
const result = await response.json();
if (result.success) {
// Fulfill the order
await fulfillOrder(result.data.reference);
}Bank Transfer Payments
For users who prefer bank transfers, generate a unique virtual bank account number per transaction. The account accepts exactly the invoiced amount and expires in 1 hour.
Generate Virtual Account
http
POST https://api.devip.me/api/v1/transfers/generate
X-Devip-Client-Id: YOUR_CLIENT_ID
Authorization: Bearer YOUR_CLIENT_SECRET
Content-Type: application/json
{
"client_id": "YOUR_CLIENT_ID",
"amount": 18000.00,
"reference": "ORDER-REF-abc123"
}json — 200 OK
{
"success": true,
"data": {
"bank_name": "Wema Bank",
"account_number": "9912847294",
"account_name": "YourApp Payments",
"amount": 18000.00,
"reference": "ORDER-REF-abc123",
"expires_at": "2026-06-15T09:00:00Z"
}
}Verify Transfer Status
Poll this endpoint or listen for a webhook to confirm the transfer was received.
http
GET https://api.devip.me/api/v1/transfers/verify/ORDER-REF-abc123
X-Devip-Client-Id: YOUR_CLIENT_ID
Authorization: Bearer YOUR_CLIENT_SECRETjson — 200 OK
{
"success": true,
"data": {
"status": "success",
"reference": "ORDER-REF-abc123"
}
}Webhooks
deVIP sends a signed POST request to your webhook URL whenever a payment event occurs. Always verify the signature before fulfilling orders.
Webhook Payload
json
POST https://yourapp.com/webhooks/devip
{
"event": "charge.success",
"data": {
"reference": "ORDER-REF-abc123",
"status": "success",
"amount": 18000.00
}
}Verifying Signatures
Compute an HMAC-SHA256 hash of the raw request body using your
webhook_secret. Compare it against the x-devip-signature header.javascript (Node.js / Express)
const crypto = require('crypto');
app.post('/webhooks/devip', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-devip-signature'];
const rawBody = req.body; // must be raw Buffer
const expected = crypto
.createHmac('sha256', process.env.DEVIP_WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return res.status(401).json({ message: 'Invalid signature' });
}
const payload = JSON.parse(rawBody);
if (payload.event === 'charge.success') {
await fulfillOrder(payload.data.reference, payload.data.amount);
}
res.status(200).json({ received: true });
});php (Laravel)
public function handleWebhook(Request $request)
{
$rawBody = $request->getContent();
$signature = $request->header('x-devip-signature');
$secret = config('devip.webhook_secret');
$expected = hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $signature)) {
return response()->json(['message' => 'Invalid signature'], 401);
}
$payload = json_decode($rawBody, true);
if ($payload['event'] === 'charge.success') {
$this->fulfillOrder(
$payload['data']['reference'],
$payload['data']['amount']
);
}
return response()->json(['received' => true]);
}python (Django)
import hmac, hashlib, json
from django.http import JsonResponse
def devip_webhook(request):
if request.method != 'POST':
return JsonResponse({'error': 'Method not allowed'}, status=405)
signature = request.headers.get('x-devip-signature', '')
raw_body = request.body
secret = settings.DEVIP_WEBHOOK_SECRET.encode('utf-8')
expected = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
return JsonResponse({'message': 'Invalid signature'}, status=401)
payload = json.loads(raw_body)
if payload['event'] == 'charge.success':
fulfill_order(payload['data']['reference'], payload['data']['amount'])
return JsonResponse({'received': True})Webhook Events
charge.successA wallet debit or bank transfer was completed successfullycharge.failedA debit attempt failed (insufficient funds, etc.)transfer.receivedA bank transfer to a virtual account was receivedSandbox & Testing
Use the sandbox endpoint to simulate a completed bank transfer payment without any real money moving. This is useful for testing your webhook handler and order fulfillment logic.
bash
# Simulate a bank transfer payment (sandbox only)
curl -X POST https://api.devip.me/api/v1/transfers/simulate-pay/ORDER-REF-abc123Calling this endpoint fires a real
charge.success webhook to your registered webhook URL so you can test your end-to-end flow without a real transfer.Test Credentials
Key
Value
client_iddevip_test_client_xxxclient_secretdevip_test_secret_xxxwebhook_secretdevip_test_whsec_xxxtest_account_idDEVIP-TEST-001Request your personal sandbox credentials from dev@devip.me.
SDKs & Libraries
Official SDKs are coming soon. In the meantime, the API is straightforward enough to integrate using any HTTP client.
Node.jsComing soon
PythonComing soon
PHP / LaravelComing soon
Quick Start — Node.js
javascript
// Install: npm install node-fetch (or use native fetch in Node 18+)
class DevipClient {
constructor(clientId, clientSecret) {
this.baseUrl = 'https://api.devip.me/api/v1';
this.headers = {
'X-Devip-Client-Id': clientId,
'Authorization': `Bearer ${clientSecret}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
};
}
async getWalletBalance(accountId) {
const res = await fetch(
`${this.baseUrl}/wallet/balance?account_id=${accountId}`,
{ headers: this.headers }
);
return res.json();
}
async debitWallet(accountId, amount, reference) {
const res = await fetch(`${this.baseUrl}/wallet/debit`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({ account_id: accountId, amount, reference }),
});
return res.json();
}
async generateVirtualAccount(clientId, amount, reference) {
const res = await fetch(`${this.baseUrl}/transfers/generate`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({ client_id: clientId, amount, reference }),
});
return res.json();
}
}
// Usage
const devip = new DevipClient(process.env.DEVIP_CLIENT_ID, process.env.DEVIP_CLIENT_SECRET);
const balance = await devip.getWalletBalance('DEVIP-12345');
console.log(balance.data.balance); // 25000.00