Overview → Getting Started
Start accepting payments in minutes
Your backend creates a payment session, receives a short-lived clientSecret, then passes it to the Vittas widget — which handles the entire checkout experience. Amount and currency are locked server-side, so they cannot be tampered with from the browser.
Before you begin
- A Vittas Core business account — sign up at core.vittasinternational.com. Business owners and admins can access the portal immediately. To grant a developer access, go to Team → Invite member and assign the Developer role.
- Node.js 18+ or any backend runtime (examples use Node.js)
- A publicly reachable HTTPS URL for webhook delivery (use webhook.site for testing)
Get your secret key
Log in to the developer portal and navigate to API Keys. Copy your secret key — you will use it on your server to create payment sessions.
| Key type | Prefix | Usage |
|---|---|---|
| Secret key | sk_test_… / sk_live_… | Server-side only — creates sessions, processes refunds, calls webhooks. |
| Client secret | cs_… | Short-lived, single-use credential returned by the session endpoint. Pass this to the widget. |
Keep your secret key safe
Never commit secret keys to version control or include them in client-side code. Use environment variables:VITTAS_SECRET_KEY=sk_test_…Create a session on your backend
Before opening the widget, your server calls the Vittas API to create a payment session. This locks the amount and currency server-side and returns a clientSecret (cs_…) that your frontend will pass to the widget.
app.post('/api/checkout', async (req, res) => { const response = await fetch( 'https://dev-api.core.vittasinternational.com/api/widget/sessions', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.VITTAS_SECRET_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ amount: 500000, // ₦5,000 in kobo (₦1 = 100 kobo) currency: 'NGN', reference: 'ORDER-001', // your idempotency key — auto-generated if omitted payerName: 'Ada Lovelace', payerEmail: 'ada@example.com', payerPhone: '+2348012345678', metadata: { orderId: 'ORD-001' }, }), } ); const { data } = await response.json(); // Return the clientSecret to your frontend — never log or store it res.json({ clientSecret: data.result.clientSecret }); });
Use dev-api for TEST, api for LIVE
https://dev-api.core.vittasinternational.com — test environment (sk_test_…)https://api.core.vittasinternational.com — production (sk_live_…)Open the widget with the client secret
Load the CDN widget script and call VittasPay.init() with the clientSecret your server returned. The widget handles the entire checkout — virtual account display, countdown, real-time confirmation.
<!-- Load from the CDN that matches your environment --> <!-- TEST: https://dev-cdn.core.vittasinternational.com/latest/widget.js --> <!-- LIVE: https://cdn.core.vittasinternational.com/latest/widget.js --> <script src="https://dev-cdn.core.vittasinternational.com/latest/widget.js"></script> <button id="pay-btn">Pay ₦5,000</button> <script> document.getElementById('pay-btn').addEventListener('click', async () => { // Fetch a fresh clientSecret from YOUR backend — never hard-code it const { clientSecret } = await fetch('/api/checkout', { method: 'POST' }) .then(r => r.json()); window.VittasPay.init({ clientSecret, onSuccess(ref) { // ref: { id, reference, status } window.location.href = '/order/success?ref=' + ref.reference; }, onCancel() { console.log('Payment cancelled.'); }, onError(err) { console.error('Widget error:', err.message); }, }); }); </script>
React / Next.js
Use the@vittascore/payment-widget-react npm package for a typed React component and hook. See the SDK reference.Listen for webhooks
Vittas sends a signed POST request to your webhook URL when payment events occur. Set your endpoint URL in the portal under Webhooks.
const express = require('express'); const crypto = require('crypto'); const app = express(); // IMPORTANT: use raw body — do NOT use express.json() before this route app.post('/webhooks/vittas', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-vittas-signature']; const rawBody = req.body; // Verify signature if (!verifySignature(signature, rawBody, process.env.VITTAS_WEBHOOK_SECRET)) { return res.status(401).send('Signature mismatch'); } const event = JSON.parse(rawBody); switch (event.type) { case 'payment.successful': fulfillOrder(event.data.reference); break; case 'payment.expired': cancelOrder(event.data.reference); break; } res.status(200).json({ received: true }); }); function verifySignature(header, body, secret) { const [tPart, v1Part] = header.split(','); const t = tPart.split('=')[1]; const v1 = v1Part.split('=')[1]; // Reject if older than 5 minutes if (Math.abs(Date.now() / 1000 - parseInt(t)) > 300) return false; const expected = crypto .createHmac('sha256', secret) .update(`${t}.${body}`) .digest('hex'); return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected)); }