Skip to content
Last updated

Webhook Integration Guide

Webhooks enable you to receive real-time notifications about NovaMed operations, including order status updates, shipment notifications, and medication request changes. This guide will help you set up and integrate webhooks into your system.


Overview

What Are Webhooks?

Webhooks are HTTP callbacks that allow NovaMed to notify your system when specific events occur. Instead of polling the API for updates, you register a webhook endpoint, and we send POST requests to your endpoint whenever events happen.

Benefits

  • Real-time updates - Receive notifications immediately when events occur
  • Efficient - No need to poll the API repeatedly
  • Reliable - Built-in retry mechanisms for failed deliveries
  • Scalable - Handles high-volume event streams efficiently

Base URLs

EnvironmentURL
Developmenthttps://novamed-feapidev.stackmod.info
Productionhttps://feapi.novamed.care

Getting Started

Step 1: Register Your Webhook Endpoint

Register your webhook URL with NovaMed:

curl -X POST https://novamed-feapidev.stackmod.info/api/external/webhook \
  -H "x-api-key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "clinic_id": "550e8400-e29b-41d4-a716-446655440000",
    "webhook_url": "https://your-domain.com/webhooks/novamed"
  }'

Response:

{
  "success": true,
  "data": {
    "webhook_id": "660e8400-e29b-41d4-a716-446655440001",
    "webhook_url": "https://your-domain.com/webhooks/novamed",
    "clinic_id": "550e8400-e29b-41d4-a716-446655440000",
    "created_at": "2025-01-15T10:00:00.000Z"
  },
  "message": "Webhook registered successfully"
}

Step 2: Set Up Your Webhook Endpoint

Your webhook endpoint must:

  • Accept HTTPS POST requests
  • Return HTTP 200 OK quickly (process asynchronously)
  • Verify the x-api-key header
  • Handle duplicate events (idempotency)

Webhook Events

NovaMed sends the following webhook events:

Order Status Events

EventDescription
order:createdNew order has been created
order:processingOrder is being processed
order:shippedOrder has been shipped
order:deliveredOrder has been delivered
order:cancelledOrder has been cancelled

Medication Request Events

EventDescription
medication_request:createdNew medication request created
medication_request:approvedMedication request approved
medication_request:filledMedication has been filled
medication_request:cancelledMedication request cancelled

Refill Events

EventDescription
refill:requestedRefill request submitted
refill:approvedRefill request approved
refill:deniedRefill request denied

Webhook Payload Structure

All webhook events follow this structure:

{
  "event_type": "order:shipped",
  "event_id": "evt_abc123xyz",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "clinic_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    // Event-specific data
  }
}

Order Shipped Event Example

{
  "event_type": "order:shipped",
  "event_id": "evt_abc123xyz",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "clinic_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "order_id": "ord_12345",
    "patient_id": "pat_67890",
    "medication_request_id": "med_req_11111",
    "tracking_number": "1Z999AA10123456784",
    "carrier": "UPS",
    "estimated_delivery": "2025-01-18",
    "shipping_address": {
      "line1": "123 Main Street",
      "city": "San Francisco",
      "state": "CA",
      "zip": "94102"
    }
  }
}

Medication Request Approved Event Example

{
  "event_type": "medication_request:approved",
  "event_id": "evt_def456xyz",
  "timestamp": "2025-01-15T09:00:00.000Z",
  "clinic_id": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "medication_request_id": "med_req_11111",
    "patient_id": "pat_67890",
    "practitioner_id": "prac_22222",
    "medication_name": "Testosterone Cypionate",
    "quantity": 1,
    "refills": 3,
    "approved_at": "2025-01-15T09:00:00.000Z"
  }
}

Implementation Examples

Node.js/Express Example

const express = require('express');
const app = express();

app.use(express.json());

// Webhook endpoint
app.post('/webhooks/novamed', async (req, res) => {
  // 1. Verify API key
  const apiKey = req.headers['x-api-key'];
  if (apiKey !== process.env.NOVAMED_WEBHOOK_SECRET) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  // 2. Return 200 immediately
  res.status(200).json({ received: true });

  // 3. Process event asynchronously
  try {
    await processWebhookEvent(req.body);
  } catch (error) {
    console.error('Error processing webhook:', error);
  }
});

async function processWebhookEvent(payload) {
  const { event_type, data, event_id } = payload;

  // Check for duplicate events
  if (await isEventProcessed(event_id)) {
    console.log('Duplicate event, skipping:', event_id);
    return;
  }

  switch (event_type) {
    case 'order:shipped':
      await handleOrderShipped(data);
      break;
    case 'order:delivered':
      await handleOrderDelivered(data);
      break;
    case 'medication_request:approved':
      await handleMedicationApproved(data);
      break;
    case 'refill:approved':
      await handleRefillApproved(data);
      break;
    default:
      console.log('Unknown event:', event_type);
  }

  // Mark event as processed
  await markEventProcessed(event_id);
}

async function handleOrderShipped(data) {
  console.log('Order shipped:', data.order_id);
  // Update your system with tracking information
  // Send notification to patient
}

async function handleOrderDelivered(data) {
  console.log('Order delivered:', data.order_id);
  // Update order status in your database
}

async function handleMedicationApproved(data) {
  console.log('Medication approved:', data.medication_request_id);
  // Trigger next steps in your workflow
}

async function handleRefillApproved(data) {
  console.log('Refill approved:', data.refill_id);
  // Process refill order
}

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});

Python/Flask Example

from flask import Flask, request, jsonify
import os
import threading

app = Flask(__name__)

@app.route('/webhooks/novamed', methods=['POST'])
def webhook_handler():
    # 1. Verify API key
    api_key = request.headers.get('x-api-key')
    if api_key != os.getenv('NOVAMED_WEBHOOK_SECRET'):
        return jsonify({'error': 'Unauthorized'}), 401
    
    # 2. Return 200 immediately
    payload = request.get_json()
    
    # 3. Process event asynchronously
    thread = threading.Thread(target=process_webhook_event, args=(payload,))
    thread.start()
    
    return jsonify({'received': True}), 200

def process_webhook_event(payload):
    event_type = payload.get('event_type')
    data = payload.get('data')
    event_id = payload.get('event_id')
    
    handlers = {
        'order:shipped': handle_order_shipped,
        'order:delivered': handle_order_delivered,
        'medication_request:approved': handle_medication_approved,
        'refill:approved': handle_refill_approved,
    }
    
    handler = handlers.get(event_type)
    if handler:
        handler(data)
    else:
        print(f'Unknown event: {event_type}')

def handle_order_shipped(data):
    print(f"Order shipped: {data.get('order_id')}")
    # Update your system with tracking information

def handle_order_delivered(data):
    print(f"Order delivered: {data.get('order_id')}")
    # Update order status

def handle_medication_approved(data):
    print(f"Medication approved: {data.get('medication_request_id')}")
    # Trigger workflow

def handle_refill_approved(data):
    print(f"Refill approved: {data.get('refill_id')}")
    # Process refill

if __name__ == '__main__':
    app.run(port=3000)

Managing Webhooks

Delete a Webhook

To remove a registered webhook:

curl -X DELETE https://novamed-feapidev.stackmod.info/api/external/webhook \
  -H "x-api-key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "clinic_id": "550e8400-e29b-41d4-a716-446655440000",
    "webhook_id": "660e8400-e29b-41d4-a716-446655440001"
  }'

Response:

{
  "success": true,
  "message": "Webhook deleted successfully"
}

Security Best Practices

1. Verify the API Key

Always verify the x-api-key header matches your webhook secret:

const apiKey = req.headers['x-api-key'];
if (apiKey !== process.env.NOVAMED_WEBHOOK_SECRET) {
  return res.status(401).json({ error: 'Unauthorized' });
}

2. Use HTTPS Only

Webhook endpoints must use HTTPS. NovaMed will not send webhooks to HTTP endpoints.

3. Implement Idempotency

Handle duplicate events gracefully using the event_id:

const processedEvents = new Map();

async function isEventProcessed(eventId) {
  return processedEvents.has(eventId);
}

async function markEventProcessed(eventId) {
  processedEvents.set(eventId, Date.now());
  // Also persist to database for reliability
}

4. Return 200 OK Quickly

Your endpoint should return HTTP 200 OK within 5 seconds. Process events asynchronously:

app.post('/webhooks/novamed', (req, res) => {
  // Return immediately
  res.status(200).json({ received: true });
  
  // Process asynchronously
  processWebhookEvent(req.body).catch(console.error);
});

Error Handling & Retries

Retry Policy

NovaMed will retry failed webhook deliveries:

AttemptDelay
1Immediate
21 minute
35 minutes
415 minutes
51 hour

After 5 failed attempts, the webhook delivery is marked as failed.

Handling Failures

If your endpoint returns a non-200 status code, the webhook will be retried. To prevent retries:

  1. Return 200 OK even if processing fails
  2. Log errors for later processing
  3. Process asynchronously to avoid timeouts

Testing Webhooks

Local Testing with ngrok

  1. Start your webhook server:

    node webhook-server.js
  2. Expose with ngrok:

    ngrok http 3000
  3. Register webhook with the ngrok URL:

    curl -X POST https://novamed-feapidev.stackmod.info/api/external/webhook \
      -H "x-api-key: your-api-key" \
      -H "Content-Type: application/json" \
      -d '{
        "clinic_id": "your-clinic-uuid",
        "webhook_url": "https://abc123.ngrok.io/webhooks/novamed"
      }'
  4. Trigger a test event by creating an order in the sandbox environment

  5. Monitor ngrok requests at http://localhost:4040


Troubleshooting

Webhooks Not Received

  • ✅ Check webhook URL is registered correctly
  • ✅ Verify endpoint is accessible (not behind firewall)
  • ✅ Ensure endpoint uses HTTPS
  • ✅ Check API key is correct

Duplicate Events

  • ✅ Implement idempotency checking using event_id
  • ✅ Store processed event IDs in database

Timeout Errors

  • ✅ Return 200 OK quickly (< 5 seconds)
  • ✅ Process events asynchronously
  • ✅ Optimize database queries

Best Practices Summary

  1. Use HTTPS - Required for webhook endpoints
  2. Verify API Key - Always check x-api-key header
  3. Return 200 OK Quickly - Within 5 seconds
  4. Process Asynchronously - Don't block the response
  5. Implement Idempotency - Handle duplicate events using event_id
  6. Log Everything - For debugging and monitoring
  7. Handle Errors Gracefully - Don't crash on errors

Next Steps

  1. Register your webhook endpoint
  2. Implement webhook handler
  3. Test with development environment
  4. Deploy to production

Support

  • API Support: api@nimbus-os.com
  • Development Environment: https://novamed-feapidev.stackmod.info
  • Production Environment: https://feapi.novamed.care

For more information, see the API Reference.