Developer Platform
APIs & Widgets
Embed live Spirit & Water metrics on any site, or integrate directly with our REST API and SSE stream. No authentication required for public read endpoints.
Quick start
The fastest way to show 238 Global data on your site is an iframe or our drop-in embed.js script. Both load pre-built widgets from /embed/* routes with minimal chrome.
Live preview — Metrics widget
Embed widgets
Three widget types are available: metrics (4 counters), feed (scrolling activity list), and globe (interactive 3-D globe with live report markers).
<iframe
src="https://238global.com/embed/metrics"
width="100%"
height="120"
frameborder="0"
title="238 Global Metrics"
></iframe><script src="https://238global.com/embed.js" data-base="https://238global.com"></script>
<div data-238="metrics" data-width="100%" data-height="120"></div>
<div data-238="feed" data-width="100%" data-height="320"></div>
<div data-238="globe" data-width="100%" data-height="400"></div>function MetricsWidget() {
return (
<iframe
src="/embed/metrics"
width="100%"
height="120"
style={{ border: 0 }}
title="238 Global Metrics"
/>
);
}Attributes
- data-238
- Widget type: metrics, feed, or globe
- data-base
- Base URL on the script tag (for cross-origin embeds)
- data-src
- Override iframe src entirely
- data-width / data-height
- Dimensions (defaults: 100% × 120 or 320)
REST API
All public read endpoints return JSON. Base path: /api/v1
/api/v1/metricsAggregate counters for Spirit infillings, water baptisms, combined events, and totals — including today's counts.
{
"spirit": 14832,
"water": 9451,
"both": 7206,
"total": 31489,
"spiritToday": 127,
"waterToday": 84,
"bothToday": 63,
"totalToday": 274,
"countries": 42,
"cities": 186,
"updatedAt": "2026-07-09T18:00:00.000Z"
}/api/v1/reportsRecent approved reports. Query params: limit (max 100), since (ISO datetime).
GET /api/v1/reports?limit=10&since=2026-07-01T00:00:00Z
{
"reports": [
{
"id": "clx…",
"city": "Lagos",
"country": "Nigeria",
"peopleCount": 23,
"type": "spirit",
"createdAt": "2026-07-09T17:45:00.000Z"
}
]
}/api/v1/streamSSEServer-Sent Events stream. On connection and every 5 seconds, two named events are emitted:
metricsAggregate counters identical to GET /api/v1/metrics.reportsObject { reports: [...] } — the 20 most recent approved reports.errorEmitted only on server-side tick failure; { message: string }.
const es = new EventSource('/api/v1/stream');
es.addEventListener('metrics', (e) => {
const metrics = JSON.parse(e.data);
console.log('Total:', metrics.total);
});
es.addEventListener('reports', (e) => {
const { reports } = JSON.parse(e.data);
console.log('Latest city:', reports[0]?.city);
});
es.addEventListener('error', (e) => {
// native EventSource error (connection lost) — reconnects automatically
// named 'error' events from server indicate a tick failure
});Authentication
Public read endpoints work without any credentials. To access higher rate limits, include your API key as a Bearer token in the Authorization header.
GET /api/v1/metrics HTTP/1.1
Authorization: Bearer 238_<your-api-key>
GET /api/v1/reports?limit=100 HTTP/1.1
Authorization: Bearer 238_<your-api-key>API keys are issued by the 238 Global team. Contact [email protected] to request a partner key.
Rate limits
Rate limits apply per IP for unauthenticated requests, or per API key for authenticated requests. Exceeding limits returns 429 Too Many Requests.
Examples
const res = await fetch('https://238global.com/api/v1/metrics');
const { total, spiritToday } = await res.json();
document.getElementById('count').textContent = total.toLocaleString();async function poll() {
const res = await fetch('/api/v1/reports?limit=5');
const { reports } = await res.json();
renderFeed(reports);
}
setInterval(poll, 30_000);
poll();Webhooks
Partner keys can subscribe to real-time event webhooks. When an event fires, 238 Global POSTs a signed JSON payload to your endpoint. Register subscriptions via the API.
Supported events
- report.created
- Fired when a report is published. Payload contains the full report object.
- metrics.updated
- Fired after each report approval with the updated aggregate metrics.
POST /api/v1/webhooks
Authorization: Bearer 238_<your-api-key>
Content-Type: application/json
{
"url": "https://yourapp.example.com/webhooks/238",
"events": ["report.created", "metrics.updated"]
}
// Response — save the secret, shown once
{
"subscription": {
"id": "sub_…",
"url": "https://yourapp.example.com/webhooks/238",
"events": ["report.created", "metrics.updated"],
"secret": "abc123…",
"createdAt": "2026-07-09T22:00:00.000Z"
}
}GET /api/v1/webhooks
Authorization: Bearer 238_<your-api-key>DELETE /api/v1/webhooks?id=sub_…
Authorization: Bearer 238_<your-api-key>Verifying signatures
Every webhook request includes an X-238-Signature header. The value is sha256=<hex> computed as HMAC-SHA256 of the raw request body using your subscription secret.
import { createHmac, timingSafeEqual } from 'crypto';
function verifyWebhook(body: string, header: string, secret: string): boolean {
const expected = 'sha256=' + createHmac('sha256', secret).update(body).digest('hex');
return timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}
// Express / Next.js route handler
app.post('/webhooks/238', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-238-signature'] as string;
if (!verifyWebhook(req.body.toString(), sig, process.env.WEBHOOK_SECRET!)) {
return res.status(401).send('Invalid signature');
}
const { event, data } = JSON.parse(req.body.toString());
console.log('Received event:', event, data);
res.sendStatus(200);
});Webhook payload structure
{
"event": "report.created",
"timestamp": "2026-07-09T22:00:00.000Z",
"data": {
"id": "clx…",
"city": "Lagos",
"country": "Nigeria",
"peopleCount": 23,
"type": "spirit",
"status": "APPROVED",
"createdAt": "2026-07-09T22:00:00.000Z"
}
}