Webhooks API
Reservoir Mastery Exclusive
Webhooks are available exclusively to Reservoir Mastery subscribers ($129/month). This premium feature includes both price and drilling intelligence webhooks. Upgrade your plan to access webhook notifications.
Overview
Webhooks allow you to receive real-time HTTP POST notifications when specific events occur in the OilPriceAPI system. Instead of polling for changes, we'll push updates directly to your server, enabling:
- Real-time price monitoring - Instant notifications when oil prices change
- Drilling intelligence alerts - Updates on rig counts, frac spreads, well permits, and DUC wells
- Automated workflows - Trigger actions based on market events
- Reduced API usage - No need for constant polling
- Better performance - Event-driven architecture
Reservoir Mastery Webhook Features
As a Reservoir Mastery subscriber, you get:
| Feature | Included |
|---|---|
| Price update webhooks | ✅ All commodities |
| Drilling intelligence webhooks | ✅ Rig counts, frac spreads, permits, DUC wells |
| Webhook endpoints | 25 |
| Events per month | 250,000 |
| Custom headers | ✅ |
| Retry logic | ✅ Exponential backoff |
| HMAC signature verification | ✅ |
| Commodity filtering | ✅ |
| Priority support | ✅ Dedicated Slack channel |
Quick Start
1. Create a Webhook Endpoint
curl -X POST https://api.oilpriceapi.com/v1/webhooks \
-H 'Authorization: Token YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://your-server.com/webhooks/oilprice",
"events": ["price.updated", "drilling.rig_count.updated"],
"commodity_filters": ["BRENT_CRUDE_USD", "WTI_USD", "US_RIG_COUNT"],
"description": "Production webhook for price monitoring"
}'
2. Handle Webhook Events
// Node.js Express example
app.post('/webhooks/oilprice', (req, res) => {
// Verify signature
const signature = req.headers['x-oilpriceapi-signature'];
const timestamp = req.headers['x-oilpriceapi-signature-timestamp'];
if (!verifyWebhookSignature(req.body, signature, timestamp)) {
return res.status(401).send('Unauthorized');
}
// Process event
const { type, data } = req.body;
switch(type) {
case 'price.updated':
console.log(`Price update: ${data.commodity} = $${data.value}`);
break;
case 'drilling.rig_count.updated':
console.log(`Rig count update: ${data.region} = ${data.value} rigs`);
break;
}
res.status(200).send('OK');
});
Alert Conditions (5 Operators)
When creating webhooks with price thresholds, use these condition operators:
1. greater_than (>)
Trigger when price exceeds a value:
{
"url": "https://your-app.com/webhook",
"events": ["price.threshold"],
"conditions": {
"commodity": "BRENT_CRUDE_USD",
"operator": "greater_than",
"value": 80.00
}
}
Use case: Alert when oil price spikes above budget threshold.
2. less_than (<)
Trigger when price drops below a value:
{
"url": "https://your-app.com/webhook",
"events": ["price.threshold"],
"conditions": {
"commodity": "WTI_USD",
"operator": "less_than",
"value": 65.00
}
}
Use case: Alert for buying opportunities when prices dip.
3. percent_increase (% up)
Trigger on percentage increase from previous value:
{
"url": "https://your-app.com/webhook",
"events": ["price.threshold"],
"conditions": {
"commodity": "NATURAL_GAS_USD",
"operator": "percent_increase",
"value": 5.0,
"timeframe": "24h"
}
}
Use case: Alert on significant price surges for trading signals.
4. percent_decrease (% down)
Trigger on percentage decrease from previous value:
{
"url": "https://your-app.com/webhook",
"events": ["price.threshold"],
"conditions": {
"commodity": "BRENT_CRUDE_USD",
"operator": "percent_decrease",
"value": 3.0,
"timeframe": "1h"
}
}
Use case: Alert on rapid price drops for risk management.
5. between (range)
Trigger when price enters or exits a range:
{
"url": "https://your-app.com/webhook",
"events": ["price.threshold"],
"conditions": {
"commodity": "WTI_USD",
"operator": "between",
"min_value": 70.00,
"max_value": 75.00,
"trigger_on": "exit"
}
}
Use case: Alert when price breaks out of trading range.
Combining Conditions
Create complex alerts with multiple conditions:
{
"url": "https://your-app.com/webhook",
"events": ["price.threshold"],
"conditions": [
{
"commodity": "WTI_USD",
"operator": "greater_than",
"value": 75.00
},
{
"commodity": "BRENT_CRUDE_USD",
"operator": "greater_than",
"value": 80.00
}
],
"condition_logic": "any"
}
| Logic | Description |
|---|---|
any | Trigger if ANY condition is met |
all | Trigger only if ALL conditions are met |
Available Events
Price Events
price.updated
Triggered whenever a commodity price is updated.
{
"id": "evt_1a2b3c4d5e",
"type": "price.updated",
"created_at": "2025-08-03T14:30:00Z",
"data": {
"commodity": "BRENT_CRUDE_USD",
"name": "Brent Crude Oil",
"value": 78.45,
"currency": "USD",
"unit": "barrel",
"change_percent": 2.3,
"previous_value": 76.69,
"timestamp": "2025-08-03T14:30:00Z"
}
}
price.significant_change
Triggered when a price changes by more than 5%.
{
"id": "evt_2b3c4d5e6f",
"type": "price.significant_change",
"created_at": "2025-08-03T14:30:00Z",
"data": {
"commodity": "NATURAL_GAS",
"name": "Natural Gas",
"value": 3.85,
"currency": "USD",
"unit": "mmBtu",
"change_percent": 6.2,
"threshold_exceeded": "5%",
"alert_type": "surge",
"timestamp": "2025-08-03T14:30:00Z"
}
}
Analytics Alert Events
Production Boost & Reservoir Mastery
Analytics alerts are available to Production Boost ($45/month) and Reservoir Mastery ($129/month) subscribers. See Price Alerts API for setup details.
analytics_alert.triggered
Triggered when a statistical or technical analysis condition is met.
Analytics Types:
z_score- Statistical anomaly detection (z-score exceeds threshold)rsi- RSI overbought/oversold signalsvolatility_spike- Volatility exceeds historical normstrend_reversal- Momentum-based trend change detection
{
"id": "evt_ana123xyz",
"type": "analytics_alert.triggered",
"created_at": "2025-12-28T10:30:00Z",
"data": {
"alert_id": "987fcdeb-51a2-3c4d-e567-890123456789",
"alert_name": "WTI Z-Score Alert",
"commodity_code": "WTI_USD",
"analytics_type": "z_score",
"condition": "z-score exceeds ±2.5",
"current_value": 2.73,
"threshold": 2.5,
"details": {
"z_score": 2.73,
"mean": 72.50,
"std_dev": 1.85,
"current_price": 77.55
},
"triggered_at": "2025-12-28T10:30:00Z"
}
}
RSI Alert Example:
{
"id": "evt_rsi456abc",
"type": "analytics_alert.triggered",
"created_at": "2025-12-28T10:30:00Z",
"data": {
"alert_id": "123e4567-e89b-12d3-a456-426614174000",
"alert_name": "Brent RSI Alert",
"commodity_code": "BRENT_CRUDE_USD",
"analytics_type": "rsi",
"condition": "RSI overbought/oversold (30/70)",
"current_value": 73.5,
"threshold": 70,
"details": {
"rsi": 73.5,
"signal": "overbought",
"current_price": 82.15
},
"triggered_at": "2025-12-28T10:30:00Z"
}
}
Drilling Intelligence Events
drilling.rig_count.updated
Weekly rig count updates from Baker Hughes.
{
"id": "evt_3c4d5e6f7g",
"type": "drilling.rig_count.updated",
"created_at": "2025-08-03T17:00:00Z",
"data": {
"commodity": "US_RIG_COUNT",
"region": "United States",
"value": 540,
"previous_value": 535,
"change": 5,
"change_percent": 0.93,
"unit": "rigs",
"source": "Baker Hughes",
"breakdown": {
"oil_rigs": 425,
"gas_rigs": 110,
"misc_rigs": 5
},
"timestamp": "2025-08-03T17:00:00Z"
}
}
drilling.frac_spread.updated
Daily hydraulic fracturing spread counts by basin.
{
"id": "evt_4d5e6f7g8h",
"type": "drilling.frac_spread.updated",
"created_at": "2025-08-03T14:00:00Z",
"data": {
"commodity": "PERMIAN_FRAC_SPREADS",
"region": "Permian Basin",
"value": 125,
"previous_value": 123,
"change": 2,
"change_percent": 1.63,
"unit": "spreads",
"source": "Primary Energy",
"timestamp": "2025-08-03T14:00:00Z"
}
}
drilling.well_permit.new
Real-time notification when a new well permit is filed. Requires Well Permits Add-on subscription.
{
"id": "evt_wp_1a2b3c4d5e",
"event": "drilling.well_permit.new",
"created_at": "2025-12-31T14:30:00Z",
"data": {
"api_number": "42-003-12345-00-00",
"api_number_raw": "42003123450000",
"state_code": "TX",
"county": "Midland",
"permit_number": "849367",
"permit_type": "new_drill",
"permit_status": "approved",
"permit_date": "2025-12-31",
"operator": {
"name": "ConocoPhillips Company",
"name_raw": "CONOCOPHILLIPS CO",
"number": "123456"
},
"well": {
"name": "UNIVERSITY 42-3 #1H",
"number": "1H",
"type": "oil"
},
"location": {
"latitude": 31.9686,
"longitude": -102.0779,
"county": "Midland",
"section": "42",
"township": "3S",
"range": "37E"
},
"target": {
"formation": "Wolfcamp",
"formation_raw": "WOLFCAMP A",
"total_depth_proposed": 12500
},
"provenance": {
"source": "texas_rrc",
"source_url": "https://www.rrc.texas.gov/...",
"fetched_at": "2025-12-31T14:25:00Z",
"data_as_of": "2025-12-31"
}
}
}
drilling.well_permits.batch
Daily batch notification with all new permits for a state. Useful for summarized updates rather than real-time.
{
"id": "evt_wpb_6f7g8h9i0j",
"event": "drilling.well_permits.batch",
"created_at": "2025-12-31T20:00:00Z",
"data": {
"state": "TX",
"count": 47,
"permits": [
{
"api_number": "42-003-12345-00-00",
"operator": {
"name": "ConocoPhillips Company",
"name_raw": "CONOCOPHILLIPS CO"
},
"well": {
"name": "UNIVERSITY 42-3 #1H",
"type": "oil"
},
"target": {
"formation": "Wolfcamp",
"total_depth_proposed": 12500
}
}
],
"truncated": false,
"batch_timestamp": "2025-12-31T20:00:00Z"
}
}
Well Permits Pricing
Well permits webhooks require a Well Permits Add-on subscription:
- Starter: 1 state ($49/mo)
- Regional: TX, OK, NM ($99/mo)
- US Complete: All US ($349/mo)
drilling.well_permit.updated (Legacy)
Daily drilling permit issuances from state agencies.
{
"id": "evt_5e6f7g8h9i",
"type": "drilling.well_permit.updated",
"created_at": "2025-08-03T15:00:00Z",
"data": {
"commodity": "TEXAS_WELL_PERMITS",
"region": "Texas",
"value": 842,
"previous_value": 798,
"change": 44,
"change_percent": 5.51,
"unit": "permits",
"source": "Texas Railroad Commission",
"timestamp": "2025-08-03T15:00:00Z"
}
}
drilling.duc_well.updated
Monthly Drilled but Uncompleted well inventories.
{
"id": "evt_6f7g8h9i0j",
"type": "drilling.duc_well.updated",
"created_at": "2025-08-15T16:00:00Z",
"data": {
"commodity": "PERMIAN_DUC_WELLS",
"region": "Permian Basin",
"value": 3542,
"previous_value": 3489,
"change": 53,
"change_percent": 1.52,
"unit": "wells",
"source": "EIA DPR",
"timestamp": "2025-08-15T16:00:00Z"
}
}
API Usage Events
api.limit.warning
Triggered when API usage reaches 80% of monthly limit.
{
"id": "evt_7g8h9i0j1k",
"type": "api.limit.warning",
"created_at": "2025-08-25T10:00:00Z",
"data": {
"resource": "api_requests",
"usage": 200000,
"limit": 250000,
"percentage": 80,
"period": "2025-08"
}
}
api.limit.exceeded
Triggered when monthly API limit is exceeded.
Subscription Events
subscription.updated
Triggered when subscription plan or status changes.
subscription.cancelled
Triggered when a subscription is cancelled.
Webhook Configuration
Creating Webhooks
POST /v1/webhooks
Request Body:
{
"url": "https://your-app.com/webhooks/oilprice",
"events": ["price.updated", "drilling.rig_count.updated"],
"commodity_filters": ["BRENT_CRUDE_USD", "US_RIG_COUNT"],
"description": "Production webhook",
"headers": {
"X-Custom-Header": "value"
},
"timeout_seconds": 30,
"max_retries": 3,
"signing_enabled": true
}
Response:
{
"id": "wh_1a2b3c4d5e6f7g8h",
"url": "https://your-app.com/webhooks/oilprice",
"status": "active",
"events": ["price.updated", "drilling.rig_count.updated"],
"commodity_filters": ["BRENT_CRUDE_USD", "US_RIG_COUNT"],
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"created_at": "2025-08-03T12:00:00Z"
}
Secret Storage
The webhook secret is only shown once during creation. Store it securely as it's required for signature verification.
Listing Webhooks
GET /v1/webhooks
Updating Webhooks
PATCH /v1/webhooks/{webhook_id}
Deleting Webhooks
DELETE /v1/webhooks/{webhook_id}
Testing Webhooks
POST /v1/webhooks/{webhook_id}/test
{
"event_type": "price.updated"
}
Security
Signature Verification
All webhooks include HMAC-SHA256 signatures for verification. Always verify signatures to ensure webhook authenticity.
Headers sent with each webhook:
X-OilPriceAPI-Event: Event typeX-OilPriceAPI-Event-ID: Unique event identifierX-OilPriceAPI-Signature: HMAC-SHA256 signatureX-OilPriceAPI-Signature-Timestamp: Unix timestamp
Verification Examples
::: code-group
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, timestamp) {
// Prevent replay attacks (5 minute window)
if (Date.now() - parseInt(timestamp) * 1000 > 300000) {
return false;
}
const payloadString = JSON.stringify(payload);
const expectedSignature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(`${payloadString}.${timestamp}`)
.digest('hex');
return signature === expectedSignature;
}
import hmac
import hashlib
import time
import json
def verify_webhook_signature(payload, signature, timestamp, secret):
# Prevent replay attacks (5 minute window)
if int(time.time()) - int(timestamp) > 300:
return False
payload_string = json.dumps(payload, separators=(',', ':'))
expected_signature = hmac.new(
secret.encode(),
f"{payload_string}.{timestamp}".encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_signature, signature)
require 'openssl'
require 'json'
def verify_webhook_signature(payload, signature, timestamp, secret)
# Prevent replay attacks (5 minute window)
return false if Time.now.to_i - timestamp.to_i > 300
payload_string = payload.to_json
expected_signature = OpenSSL::HMAC.hexdigest(
'SHA256',
secret,
"#{payload_string}.#{timestamp}"
)
ActiveSupport::SecurityUtils.secure_compare(expected_signature, signature)
end
function verifyWebhookSignature($payload, $signature, $timestamp, $secret) {
// Prevent replay attacks (5 minute window)
if (time() - intval($timestamp) > 300) {
return false;
}
$payloadString = json_encode($payload);
$expectedSignature = hash_hmac(
'sha256',
$payloadString . '.' . $timestamp,
$secret
);
return hash_equals($expectedSignature, $signature);
}
:::
Delivery & Reliability
Retry Logic
Failed webhook deliveries are automatically retried with exponential backoff:
- First retry: 1 minute after failure
- Second retry: 5 minutes after first retry
- Third retry: 15 minutes after second retry
After 3 failed attempts, the webhook event is marked as failed and can be retrieved via the API.
Delivery Requirements
Your webhook endpoint must:
- Accept HTTPS requests (TLS 1.2 or higher)
- Respond within 30 seconds
- Return a 2xx status code for successful receipt
- Handle duplicate deliveries idempotently
Monitoring Deliveries
View webhook event history and delivery status:
GET /v1/webhooks/{webhook_id}/events
Response:
{
"webhook_events": [
{
"id": "we_1a2b3c4d5e",
"event_id": "evt_9z8y7x6w5v",
"event_type": "price.updated",
"status": "delivered",
"attempts": 1,
"response_status_code": 200,
"delivery_duration_ms": 245,
"created_at": "2025-08-03T14:30:00Z"
}
],
"pagination": {
"current_page": 1,
"total_pages": 10,
"total_count": 248
}
}
Best Practices
1. Idempotency
Always handle webhooks idempotently using the event_id:
// Store processed event IDs to prevent duplicate processing
const processedEvents = new Set();
app.post('/webhook', (req, res) => {
const eventId = req.body.id;
if (processedEvents.has(eventId)) {
return res.status(200).send('Already processed');
}
// Process event
processWebhook(req.body);
processedEvents.add(eventId);
res.status(200).send('OK');
});
2. Async Processing
Acknowledge webhooks quickly and process asynchronously:
app.post('/webhook', async (req, res) => {
// Quick validation
if (!isValidWebhook(req)) {
return res.status(401).send('Unauthorized');
}
// Acknowledge immediately
res.status(200).send('OK');
// Process asynchronously
await jobQueue.add('process-webhook', {
event: req.body,
receivedAt: new Date()
});
});
3. Error Handling
Implement robust error handling:
app.post('/webhook', async (req, res) => {
try {
await processWebhook(req.body);
res.status(200).send('OK');
} catch (error) {
console.error('Webhook processing error:', error);
// Return 500 to trigger retry
res.status(500).send('Internal error');
}
});
4. Filtering
Use commodity filters to reduce noise:
{
"url": "https://your-app.com/webhook",
"events": ["price.updated"],
"commodity_filters": ["BRENT_CRUDE_USD", "WTI_USD"],
"description": "Only crude oil prices"
}
Use Cases
Real-Time Trading Dashboard
// Update trading dashboard when prices change
app.post('/webhooks/prices', async (req, res) => {
const { type, data } = req.body;
if (type === 'price.updated') {
// Update in-memory cache
priceCache.set(data.commodity, data);
// Broadcast to connected clients
io.emit('price-update', {
commodity: data.commodity,
price: data.value,
change: data.change_percent,
timestamp: data.timestamp
});
// Check trading rules
await checkTradingAlerts(data);
}
res.status(200).send('OK');
});
Drilling Activity Monitor
// Monitor drilling activity for investment decisions
app.post('/webhooks/drilling', async (req, res) => {
const { type, data } = req.body;
switch(type) {
case 'drilling.rig_count.updated':
await updateRigCountDashboard(data);
await checkSupplyIndicators(data);
break;
case 'drilling.frac_spread.updated':
await updateCompletionActivity(data);
await forecastProduction(data);
break;
case 'drilling.duc_well.updated':
await analyzeInventoryTrends(data);
await predictCompletionTiming(data);
break;
}
res.status(200).send('OK');
});
Automated Alerts
// Send alerts based on significant changes
app.post('/webhooks/alerts', async (req, res) => {
const { type, data } = req.body;
if (type === 'price.significant_change') {
// Send email alert
await sendEmail({
to: alertRecipients,
subject: `${data.alert_type.toUpperCase()}: ${data.commodity}`,
body: `${data.name} ${data.alert_type} by ${data.change_percent}% to $${data.value}`
});
// Send SMS for critical commodities
if (criticalCommodities.includes(data.commodity)) {
await sendSMS({
to: smsRecipients,
message: `ALERT: ${data.commodity} ${data.alert_type} ${data.change_percent}%`
});
}
// Trigger trading actions
await executeTradingStrategy(data);
}
res.status(200).send('OK');
});
Troubleshooting
Common Issues
Webhook Not Receiving Events
Check endpoint status:
GET /v1/webhooks/{webhook_id}Ensure status is "active" and no consecutive failures.
Verify URL accessibility:
- Must be HTTPS
- Must be publicly accessible
- Check firewall rules
Review event filters:
- Ensure subscribed to correct events
- Check commodity filters
Signature Verification Failing
Check timestamp:
- Ensure server time is synchronized
- Verify timestamp parsing
Verify payload formatting:
- Use raw request body, not parsed JSON
- Ensure no middleware is modifying the body
Confirm secret:
- Use the exact secret from webhook creation
- Check for trailing spaces or newlines
Missing Events
Check delivery logs:
GET /v1/webhooks/{webhook_id}/eventsVerify subscription:
- Must have active Reservoir Mastery subscription
- Check monthly event limits
Review filters:
- Commodity filters may be too restrictive
- Event types might not match expectations
Support
For webhook implementation support:
- Documentation: docs.oilpriceapi.com/webhooks
- Email: [email protected]
- Reservoir Mastery: Dedicated Slack channel for priority support
Testing & Development
Webhook Test Harness
We provide an open-source Webhook Test Harness for local development and testing. This tool lets you:
- Capture and inspect webhooks in real-time with a web dashboard
- Verify signatures to ensure your verification code is working correctly
- Test all 14 event types with realistic sample payloads
- Debug webhook payloads before deploying to production
Quick Start
# Clone the harness
git clone https://github.com/OilpriceAPI/webhook-test-harness.git
cd webhook-test-harness
# Install and run
npm install
npm start
# Open dashboard at http://localhost:3333
Create a Public Tunnel
To receive real webhooks from OilPriceAPI, expose your local harness:
# Cloudflare Tunnel (recommended - free, no signup)
cloudflared tunnel --url http://localhost:3333
# You'll get a URL like: https://random-words.trycloudflare.com
# Register this URL + /webhook in your OilPriceAPI dashboard
Configure Webhook Secret
After creating a webhook in the OilPriceAPI dashboard, configure the secret:
curl -X POST http://localhost:3333/api/secret \
-H "Content-Type: application/json" \
-d '{"secret": "YOUR_WEBHOOK_SECRET_HERE"}'
Resources
- GitHub Repository: github.com/OilpriceAPI/webhook-test-harness
- CLI Commands: Send test webhooks, list captures, view statistics
- Docker Support: One-command deployment with
docker-compose up
See the full documentation for all features and configuration options.
Limits & Performance
As a Reservoir Mastery subscriber, you get:
- 25 webhook endpoints - Configure multiple endpoints for different use cases
- 250,000 events per month - Generous limit for all your webhook needs
- 500 events per minute - High-throughput delivery rate
- 30-second timeout - Ample time for processing
- 3 retry attempts - Reliable delivery with exponential backoff
Changelog
Version 2.0 (August 2025)
- Webhooks now exclusive to Reservoir Mastery plan
- Added comprehensive drilling intelligence webhooks
- Improved retry logic with exponential backoff
- Added commodity filtering
- Enhanced security with replay protection
Version 1.0 (January 2025)
- Initial webhook implementation
- Price update events
- Basic retry logic
- HMAC signature verification