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');
});
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"
}
}
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.updated
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}/events
Verify 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
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