A local development tool for capturing, verifying, and inspecting OilPriceAPI webhooks in real-time.
Documentation Links:
- OilPriceAPI Webhooks Guide - Complete webhook API documentation
- Signature Verification Guide - Security best practices
- OilPriceAPI Dashboard - Manage your webhooks
- Real-time Dashboard - Watch webhooks arrive instantly via WebSocket
- Signature Verification - Automatic HMAC-SHA256 validation
- Dynamic Secret Configuration - Set webhook secret via UI or API without restart
- 14 Event Types - Support for all OilPriceAPI webhook events
- 14 Commodities - WTI, Brent, Natural Gas, Gold, Forex, and more
- CLI Tool - Send test webhooks and manage captures
- SQLite Storage - Persistent webhook history
- Docker Ready - One-command deployment
- Tunnel Friendly - Works with ngrok, Cloudflare Tunnel, localtunnel
# Clone the repo
git clone https://github.com/OilpriceAPI/webhook-test-harness.git
cd webhook-test-harness
# Install dependencies
npm install
# Start the harness
npm startOpen http://localhost:3333 to see the dashboard.
To receive real webhooks from OilPriceAPI, you need a public URL. Here's the complete flow:
npm startChoose one of these options:
Option A: Cloudflare Tunnel (Recommended - Free, no signup)
# Install cloudflared: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/
cloudflared tunnel --url http://localhost:3333
# You'll get a URL like: https://random-words.trycloudflare.comOption B: ngrok (Requires free account)
npx ngrok http 3333
# You'll get a URL like: https://abc123.ngrok.ioOption C: localtunnel (Free, less stable)
npx localtunnel --port 3333
# You'll get a URL like: https://random-subdomain.loca.lt- Go to OilPriceAPI Dashboard
- Click New Webhook
- Enter your tunnel URL +
/webhook(e.g.,https://random-words.trycloudflare.com/webhook) - Select events you want to receive
- Click Save - copy the Webhook Secret shown
You have two options:
Via API (Recommended):
curl -X POST http://localhost:3333/api/secret \
-H "Content-Type: application/json" \
-d '{"secret": "YOUR_WEBHOOK_SECRET_HERE"}'Via Environment Variable (on startup):
WEBHOOK_SECRET=your_secret_here npm startIn the OilPriceAPI dashboard, click Test on your webhook. You should see:
- The event appear in the harness dashboard
- Signature: valid (green) if secret is configured correctly
The web dashboard at http://localhost:3333 provides:
+------------------------------------------------------------------+
| OPA Webhook Test Harness [Settings] [Clear] |
+------------------------------------------------------------------+
| |
| Total: 42 webhooks captured |
| |
| [All Events ▼] [All Commodities ▼] |
| |
| +------------------------------------------------------------+ |
| | price.updated | WTI_USD | Signature: valid ✓ | |
| | 1/3/2026, 12:33:11 PM | | | |
| +------------------------------------------------------------+ |
| | price.significant_change | BRENT | Signature: valid ✓ | |
| | 1/3/2026, 12:32:46 PM | | | |
| +------------------------------------------------------------+ |
| |
+------------------------------------------------------------------+
- Real-time Updates - Events appear instantly via Socket.io
- Event Filtering - Filter by event type or commodity
- Payload Inspector - Click any event to see full JSON payload
- Signature Status - Green checkmark for valid, red X for invalid
- Clear All - Reset captured webhooks
# Send a single price update
npm run cli -- trigger -e price.updated -c WTI_USD
# Send all 14 event types
npm run cli -- trigger --all
# Send price.updated for all 14 commodities
npm run cli -- trigger --all-commodities
# With signature (for testing verification)
npm run cli -- trigger --all --secret YOUR_WEBHOOK_SECRET# List all captured webhooks
npm run cli -- list
# Filter by event type
npm run cli -- list -e price.updated
# Filter by commodity
npm run cli -- list -c WTI_USD
# Show latest 10
npm run cli -- list --limit 10npm run cli -- stats # Show statistics
npm run cli -- clear # Clear all captured webhooks
npm run cli -- events # List all 14 event types
npm run cli -- commodities # List all 14 commodities
npm run cli -- verify # Manually verify a signature
npm run cli -- help # Show all commandsOilPriceAPI sends webhooks for these events:
| Category | Event | Description |
|---|---|---|
| Price | price.updated |
Any price change |
price.significant_change |
Major price movement (>1%) | |
price.threshold |
Custom threshold crossed | |
| Drilling | drilling.rig_count.updated |
Baker Hughes rig count update |
drilling.frac_spread.updated |
Frac spread count update | |
drilling.well_permit.new |
New well permit issued | |
drilling.well_permit.updated |
Well permit status change | |
drilling.well_permits.batch |
Batch of permit updates | |
drilling.duc_well.updated |
DUC well inventory change | |
| API | api.limit.warning |
Approaching usage limit (80%) |
api.limit.exceeded |
Usage limit reached | |
| Subscription | subscription.updated |
Plan changed |
subscription.cancelled |
Subscription cancelled | |
| Analytics | analytics_alert.triggered |
Custom analytics alert |
All 14 commodities supported by OilPriceAPI:
| Code | Name |
|---|---|
WTI_USD |
West Texas Intermediate Crude |
BRENT_CRUDE_USD |
Brent Crude Oil |
NATURAL_GAS_USD |
US Natural Gas (Henry Hub) |
NATURAL_GAS_GBP |
UK Natural Gas (NBP) |
COAL_USD |
Coal (Newcastle) |
GOLD_USD |
Gold Spot |
GBP_USD |
British Pound / US Dollar |
EUR_USD |
Euro / US Dollar |
DUBAI_CRUDE_USD |
Dubai Crude Oil |
DUTCH_TTF_EUR |
Dutch TTF Natural Gas |
MGO_05S_USD |
Marine Gas Oil 0.5% Sulfur |
VLSFO_USD |
Very Low Sulfur Fuel Oil |
HFO_380_USD |
Heavy Fuel Oil 380 CST |
HFO_180_USD |
Heavy Fuel Oil 180 CST |
OilPriceAPI signs all webhooks using HMAC-SHA256 for security.
Every webhook includes these headers:
| Header | Description |
|---|---|
X-OilPriceAPI-Signature |
HMAC-SHA256 signature (hex) |
X-OilPriceAPI-Signature-Timestamp |
Unix timestamp |
X-OilPriceAPI-Event |
Event type (e.g., price.updated) |
X-OilPriceAPI-Event-ID |
Unique event ID (UUID) |
The signature is computed as:
const crypto = require('crypto');
function verifyWebhook(payload, signature, timestamp, secret) {
// Signature payload format: {json}.{timestamp}
const signaturePayload = `${JSON.stringify(payload)}.${timestamp}`;
const expected = crypto
.createHmac('sha256', secret)
.update(signaturePayload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}- Always verify signatures in production - Prevents spoofed webhooks
- Check timestamp freshness - Reject webhooks older than 5 minutes
- Use constant-time comparison - Prevents timing attacks
- Store secrets securely - Use environment variables, not code
| Endpoint | Method | Description |
|---|---|---|
/webhook |
POST | Receive webhooks from OilPriceAPI |
/api/events |
GET | List captured events (supports ?eventType=, ?commodity=, ?limit=, ?offset=) |
/api/events/:id |
GET | Get single event by ID |
/api/events |
DELETE | Clear all captured events |
/api/stats |
GET | Get capture statistics |
/api/secret |
GET | Check if secret is configured |
/api/secret |
POST | Set webhook secret dynamically |
/api/secret |
DELETE | Clear webhook secret |
/health |
GET | Health check endpoint |
Set the webhook secret at runtime without restarting:
# Set secret
curl -X POST http://localhost:3333/api/secret \
-H "Content-Type: application/json" \
-d '{"secret": "your_webhook_secret_here"}'
# Response:
# {"success": true, "message": "Webhook secret configured", "preview": "7ea1...ac31"}
# Check status
curl http://localhost:3333/api/secret
# {"configured": true, "preview": "7ea1...ac31"}
# Clear secret
curl -X DELETE http://localhost:3333/api/secret
# {"success": true, "message": "Webhook secret cleared"}# Build and run
docker-compose up --build
# Run in background
docker-compose up -d
# View logs
docker-compose logs -f
# Stop
docker-compose downWEBHOOK_SECRET=your_secret docker-compose upversion: '3.8'
services:
webhook-harness:
build: .
ports:
- "3333:3333"
environment:
- PORT=3333
- WEBHOOK_SECRET=${WEBHOOK_SECRET:-}
volumes:
- ./data:/app/data # Persist webhook database| Variable | Description | Default |
|---|---|---|
PORT |
HTTP server port | 3333 |
WEBHOOK_SECRET |
Your OilPriceAPI webhook secret (can also set via API) | (none) |
OPA_API_KEY |
API key for production webhook testing | (none) |
cp .env.example .env
# Edit .env with your valuesopa-webhook-harness/
├── server/
│ ├── index.js # Express server + Socket.io + API
│ ├── db.js # SQLite storage layer
│ └── signatureVerifier.js # HMAC-SHA256 verification
├── cli/
│ ├── index.js # CLI commands
│ └── samplePayloads.js # Test payload generators
├── dashboard/
│ └── index.html # Web UI (Tailwind + Alpine.js + Socket.io)
├── scripts/
│ ├── test-all-events.sh # Test all event types
│ ├── test-all-commodities.sh
│ └── full-test.sh
├── data/
│ └── webhooks.db # SQLite database (gitignored)
├── package.json
├── docker-compose.yml
├── Dockerfile
├── .env.example
└── README.md
| Tool | Pros | Cons |
|---|---|---|
| Cloudflare Tunnel | Free, no signup, reliable | URL changes on restart |
| ngrok | Stable URLs (paid), great UI | Free tier requires signup, limits |
| localtunnel | Free, no signup | Less stable, password prompt |
| Tailscale | Permanent, secure | Requires setup |
# Install (macOS)
brew install cloudflare/cloudflare/cloudflared
# Install (Linux)
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o cloudflared
chmod +x cloudflared
sudo mv cloudflared /usr/local/bin/
# Run
cloudflared tunnel --url http://localhost:3333- Check tunnel is running - The URL should be accessible from the internet
- Verify webhook URL - Must include
/webhookpath (e.g.,https://xyz.trycloudflare.com/webhook) - Check OilPriceAPI dashboard - Webhook status should be "active"
- Test manually:
curl -X POST https://your-tunnel-url.com/webhook \ -H "Content-Type: application/json" \ -d '{"event": "test", "data": {}}'
- Set the secret via API:
curl -X POST http://localhost:3333/api/secret \ -H "Content-Type: application/json" \ -d '{"secret": "YOUR_SECRET"}'
- Or restart with environment variable:
WEBHOOK_SECRET=your_secret npm start
- Copy secret exactly - Secrets are case-sensitive
- Check for whitespace - No leading/trailing spaces
- Verify it's the correct webhook - Each webhook has a unique secret
- Check timestamp - Webhooks older than 5 minutes may be rejected
- Check connection indicator - Should show "Connected" in top-right
- Refresh the page - Reconnects Socket.io
- Check server is running -
curl http://localhost:3333/health
# Find and kill the process
lsof -i :3333
kill -9 <PID>
# Or use a different port
PORT=4444 npm start{
"id": "079dfeb2-6391-4e52-88fd-6f8e149cea6c",
"event": "price.updated",
"created_at": "2026-01-03T17:39:28Z",
"data": {
"commodity": "WTI_USD",
"commodity_code": "WTI_USD",
"name": "spot_price",
"value": 72.45,
"previous_value": 72.38,
"change_percent": 0.097,
"currency": "USD",
"unit": "barrel",
"source": "oilprice.business_insider",
"timestamp": "2026-01-03T17:39:28Z"
}
}Contributions welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT License - see LICENSE for details.
Built with care for the OilPriceAPI community.
Resources:
- OilPriceAPI Webhooks Documentation - Full API reference
- Signature Verification Guide - Security implementation
- API Documentation - Complete API reference
Questions? Open an issue or contact [email protected]