OilPriceAPI Documentation
GitHub
GitHub

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:

FeatureIncluded
Price update webhooks✅ All commodities
Drilling intelligence webhooks✅ Rig counts, frac spreads, permits, DUC wells
Webhook endpoints25
Events per month250,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 type
  • X-OilPriceAPI-Event-ID: Unique event identifier
  • X-OilPriceAPI-Signature: HMAC-SHA256 signature
  • X-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:

  1. First retry: 1 minute after failure
  2. Second retry: 5 minutes after first retry
  3. 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

  1. Check endpoint status:

    GET /v1/webhooks/{webhook_id}
    

    Ensure status is "active" and no consecutive failures.

  2. Verify URL accessibility:

    • Must be HTTPS
    • Must be publicly accessible
    • Check firewall rules
  3. Review event filters:

    • Ensure subscribed to correct events
    • Check commodity filters

Signature Verification Failing

  1. Check timestamp:

    • Ensure server time is synchronized
    • Verify timestamp parsing
  2. Verify payload formatting:

    • Use raw request body, not parsed JSON
    • Ensure no middleware is modifying the body
  3. Confirm secret:

    • Use the exact secret from webhook creation
    • Check for trailing spaces or newlines

Missing Events

  1. Check delivery logs:

    GET /v1/webhooks/{webhook_id}/events
    
  2. Verify subscription:

    • Must have active Reservoir Mastery subscription
    • Check monthly event limits
  3. 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