Wave Business reporting = revenue visibility + business KPIs. Analytics dashboard + automatic exports = 2026 modern SME standard.
TL;DR
- Essential KPIs: transactions, conversion rate, revenue, refunds.
- Stack: PostgreSQL + Metabase (free).
- Cron daily reports email + Slack.
- M-1, Y-1 comparisons, growth tracking.
Essential Wave KPIs
`
- Transactions:
- Daily volume
- Average tickets
- Success vs failure rate
- Revenue:
- Gross / net revenue (after Wave fees)
- Per product / category
- Per channel (web, app)
- Customer behavior:
- Checkout → payment conversions
- Payment abandonments (+ reasons)
- Repeat customers
- Operational:
- Refunds / chargebacks
- Failed payments
- Reconciliation gaps
`
2026 reporting stack
- PostgreSQL (normalized Wave transactions DB)
- +
- Metabase (free BI, self-host or cloud)
- or
- PowerBI / Looker Studio
- +
- Daily report cron jobs
- +
- Slack webhook alerts
Analytics DB schema
`sql
-- Denormalized analytics table
CREATE TABLE wave_transactions_analytics (
id UUID PRIMARY KEY,
wave_id VARCHAR UNIQUE,
amount INT,
currency VARCHAR(3),
status VARCHAR(20),
type VARCHAR(20), -- 'checkout' | 'payout' | 'refund'
-- Dates
created_at TIMESTAMP,
paid_at TIMESTAMP,
date DATE GENERATED ALWAYS AS (DATE(created_at)) STORED,
hour INT GENERATED ALWAYS AS (EXTRACT(HOUR FROM created_at)) STORED,
weekday INT GENERATED ALWAYS AS (EXTRACT(DOW FROM created_at)) STORED,
-- Customer
customer_id UUID,
customer_phone VARCHAR,
is_repeat_customer BOOLEAN,
-- Product / order
order_id UUID,
product_category VARCHAR,
product_name VARCHAR,
-- Wave details
wave_fee INT,
net_amount INT,
device VARCHAR,
-- Indexes
INDEX idx_date (date),
INDEX idx_status (status),
INDEX idx_customer (customer_id)
);
`
Standard SQL queries
`sql
-- 1. Daily revenue last 30 days
SELECT
date,
COUNT(*) AS transactions,
SUM(amount) AS gross_revenue,
SUM(net_amount) AS net_revenue,
AVG(amount) AS avg_ticket
FROM wave_transactions_analytics
WHERE status = 'completed'
AND date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY date
ORDER BY date DESC;
-- 2. Top 10 products
SELECT
product_category,
product_name,
COUNT(*) AS sales,
SUM(amount) AS revenue
FROM wave_transactions_analytics
WHERE status = 'completed'
AND date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY product_category, product_name
ORDER BY revenue DESC
LIMIT 10;
-- 3. Peak conversion hours
SELECT
hour,
COUNT(*) AS transactions,
AVG(amount) AS avg_ticket
Need a professional website?
Kolonell builds websites that attract clients, optimized for the Sénégalese market. Free quote in 2 minutes.
FROM wave_transactions_analytics
WHERE status = 'completed'
GROUP BY hour
ORDER BY transactions DESC;
-- 4. Customer cohort retention
WITH cohorts AS (
SELECT
customer_id,
DATE_TRUNC('month', MIN(created_at)) AS cohort_month
FROM wave_transactions_analytics
WHERE status = 'completed'
GROUP BY customer_id
)
SELECT
cohort_month,
DATE_TRUNC('month', t.created_at) AS activity_month,
COUNT(DISTINCT t.customer_id) AS active_customers
FROM wave_transactions_analytics t
JOIN cohorts c ON t.customer_id = c.customer_id
GROUP BY cohort_month, activity_month
ORDER BY cohort_month, activity_month;
`
Daily report cron
`typescript
cron.schedule('0 8 * * *', async () => { // 8am every day
const yesterday = new Date(Date.now() - 24 * 3600 * 1000)
.toISOString().slice(0, 10);
const stats = await db.query(`
SELECT
COUNT(*) AS transactions,
SUM(amount) AS revenue,
SUM(amount) FILTER (WHERE status = 'completed') AS net_revenue,
COUNT(*) FILTER (WHERE status = 'failed') AS failed,
COUNT(*) FILTER (WHERE type = 'refund') AS refunds,
AVG(amount) AS avg_ticket
FROM wave_transactions_analytics
WHERE date = $1
`, [yesterday]);
// Send Slack
await slackWebhook({
text: 📊 Wave Report ${yesterday},
blocks: [
{ type: 'section', text: { type: 'mrkdwn', text:
*Transactions*: ${stats.transactions}\n*Revenue*: ${stats.revenue.toLocaleString()} XOF\n*Failed*: ${stats.failed}\n*Refunds*: ${stats.refunds}\n*Avg ticket*: ${stats.avg_ticket.toLocaleString()}
} },
],
});
// Email finance team
await sendEmail('finance@kolonell.com', {
subject: Wave Report ${yesterday},
body: renderEmailTemplate(stats),
});
});
`
Free Metabase setup
`bash
# Self-host Metabase
docker run -d -p 3000:3000 \
-v metabase-data:/metabase-data \
--name metabase metabase/metabase
# Connect to PostgreSQL
# Create dashboards:
# - Daily revenue
# - Top products
# - Customer retention
# - Failed transactions
`
Cost: 0€ self-host, 80-300€/month cloud.
FAQ
Q: Wave provides analytics dashboard?
A: Basic. CSV export + custom analytics = recommended for deep insights.
Q: Real-time vs daily?
A: Real-time for critical ops (refunds, failed). Daily for business reporting.
Conclusion
2026 Wave Business reporting: PostgreSQL + Metabase + daily cron = modern SME stack. Critical business + operational KPIs. Free self-host, cloud 80-300€/month.
Mohamed Bah
Fondateur, Kolonell
Passionate about digital and entrepreneurship in Africa, Mohamed has been helping Sénégalese businesses with their digital transformation since 2020. Founder of Kolonell, he believes every SME deserves a professional and accessible online présence.

