{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Webhook Integration Guide","description":"API documentation for integrating with NovaMed - healthcare partner integration platform","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"webhook-integration-guide","__idx":0},"children":["Webhook Integration Guide"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["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."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"overview","__idx":1},"children":["Overview"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"what-are-webhooks","__idx":2},"children":["What Are Webhooks?"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["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."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"benefits","__idx":3},"children":["Benefits"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Real-time updates"]}," - Receive notifications immediately when events occur"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Efficient"]}," - No need to poll the API repeatedly"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Reliable"]}," - Built-in retry mechanisms for failed deliveries"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Scalable"]}," - Handles high-volume event streams efficiently"]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"base-urls","__idx":4},"children":["Base URLs"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Environment"},"children":["Environment"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"URL"},"children":["URL"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Development"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://novamed-feapidev.nimbushealthcaretest.com"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Production"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://feapi.novamed.care"]}]}]}]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"getting-started","__idx":5},"children":["Getting Started"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-1-register-your-webhook-endpoint","__idx":6},"children":["Step 1: Register Your Webhook Endpoint"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Register your webhook URL with NovaMed:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -X POST https://novamed-feapidev.nimbushealthcaretest.com/api/external/webhook \\\n  -H \"x-api-key: your-api-key-here\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json\" \\\n  -d '{\n    \"clinic_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"webhook_url\": \"https://your-domain.com/webhooks/novamed\"\n  }'\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Response"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"success\": true,\n  \"data\": {\n    \"webhook_id\": \"660e8400-e29b-41d4-a716-446655440001\",\n    \"webhook_url\": \"https://your-domain.com/webhooks/novamed\",\n    \"clinic_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"created_at\": \"2025-01-15T10:00:00.000Z\"\n  },\n  \"message\": \"Webhook registered successfully\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-2-set-up-your-webhook-endpoint","__idx":7},"children":["Step 2: Set Up Your Webhook Endpoint"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your webhook endpoint must:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Accept HTTPS POST requests"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Return HTTP 200 OK quickly (process asynchronously)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Verify the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-api-key"]}," header"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Handle duplicate events (idempotency)"]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"webhook-events","__idx":8},"children":["Webhook Events"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["NovaMed sends the following webhook events:"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"medication-order-events","__idx":9},"children":["Medication Order Events"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Event"},"children":["Event"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["medication_order:verified"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Medication order has been verified"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["medication_order:submitted"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Medication order has been submitted"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["medication_order:acknowledged"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Medication order has been acknowledged"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["medication_order:filled"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Medication order has been filled"]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"shipment-events","__idx":10},"children":["Shipment Events"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Event"},"children":["Event"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["shipment:created"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Shipment has been created (includes tracking info)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["shipment:cancelled"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Shipment has been cancelled"]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"practitioner-events","__idx":11},"children":["Practitioner Events"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Event"},"children":["Event"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["practitioner:activated"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Practitioner account has been activated"]}]}]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"webhook-payload-structure","__idx":12},"children":["Webhook Payload Structure"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["All webhook events follow this structure:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"event_name\": \"medication_order:verified\",\n  \"event_data\": {\n    // Event-specific data\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"medication-order-event-example","__idx":13},"children":["Medication Order Event Example"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"event_name\": \"medication_order:verified\",\n  \"event_data\": {\n    \"id\": \"a7570e3c-4338-485f-9465-ee09793c2d46\",\n    \"clinic_id\": \"2a7d8da1-2f69-46e1-87a4-04490ab73c41\",\n    \"patient_id\": \"8456ce26-05db-4fcd-bebd-495bd7bc04df\",\n    \"practitioner_id\": \"a7570e3c-4338-485f-9465-ee09793c2d46\",\n    \"rx_number\": \"123456\"\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Note:"]}," The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["rx_number"]}," field is not available for all events."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"shipment-created-event-example","__idx":14},"children":["Shipment Created Event Example"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After a shipment is created, additional fields are included:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"event_name\": \"shipment:created\",\n  \"event_data\": {\n    \"id\": \"a7570e3c-4338-485f-9465-ee09793c2d46\",\n    \"clinic_id\": \"2a7d8da1-2f69-46e1-87a4-04490ab73c41\",\n    \"patient_id\": \"8456ce26-05db-4fcd-bebd-495bd7bc04df\",\n    \"practitioner_id\": \"a7570e3c-4338-485f-9465-ee09793c2d46\",\n    \"rx_number\": \"123456\",\n    \"shipping_label\": \"https://example.com/shipping-label.pdf\",\n    \"shipment_id\": \"8456ce26-05db-4fcd-bebd-495bd7bc04df\",\n    \"shipment_provider\": \"Ameriship\",\n    \"tracking_url\": \"https://example.com/tracking\"\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"practitioner-activated-event-example","__idx":15},"children":["Practitioner Activated Event Example"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"event_name\": \"practitioner:activated\",\n  \"event_data\": {\n    \"practitioner_id\": \"4f407bca-2a9b-4dde-8240-e53331a5a986\",\n    \"practitioner_name\": \"Dr. John Smith\",\n    \"practitioner_email\": \"dr.smith@clinic.com\",\n    \"practitioner_phone\": \"+1-555-0123\",\n    \"practitioner_npi\": \"1234567890\",\n    \"practitioner_status\": \"active\"\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"implementation-examples","__idx":16},"children":["Implementation Examples"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"nodejsexpress-example","__idx":17},"children":["Node.js/Express Example"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"const express = require('express');\nconst app = express();\n\napp.use(express.json());\n\n// Webhook endpoint\napp.post('/webhooks/novamed', async (req, res) => {\n  // 1. Verify API key\n  const apiKey = req.headers['x-api-key'];\n  if (apiKey !== process.env.NOVAMED_WEBHOOK_SECRET) {\n    return res.status(401).json({ error: 'Unauthorized' });\n  }\n\n  // 2. Return 200 immediately\n  res.status(200).json({ received: true });\n\n  // 3. Process event asynchronously\n  try {\n    await processWebhookEvent(req.body);\n  } catch (error) {\n    console.error('Error processing webhook:', error);\n  }\n});\n\nasync function processWebhookEvent(payload) {\n  const { event_name, event_data } = payload;\n\n  // Check for duplicate events using medication order ID\n  if (event_data.id && await isEventProcessed(event_data.id, event_name)) {\n    console.log('Duplicate event, skipping:', event_name, event_data.id);\n    return;\n  }\n\n  switch (event_name) {\n    case 'medication_order:verified':\n      await handleMedicationOrderVerified(event_data);\n      break;\n    case 'medication_order:submitted':\n      await handleMedicationOrderSubmitted(event_data);\n      break;\n    case 'medication_order:acknowledged':\n      await handleMedicationOrderAcknowledged(event_data);\n      break;\n    case 'medication_order:filled':\n      await handleMedicationOrderFilled(event_data);\n      break;\n    case 'shipment:created':\n      await handleShipmentCreated(event_data);\n      break;\n    case 'shipment:cancelled':\n      await handleShipmentCancelled(event_data);\n      break;\n    case 'practitioner:activated':\n      await handlePractitionerActivated(event_data);\n      break;\n    default:\n      console.log('Unknown event:', event_name);\n  }\n\n  // Mark event as processed\n  if (event_data.id) {\n    await markEventProcessed(event_data.id, event_name);\n  }\n}\n\nasync function handleMedicationOrderVerified(data) {\n  console.log('Medication order verified:', data.id);\n  // Update order status in your system\n}\n\nasync function handleMedicationOrderSubmitted(data) {\n  console.log('Medication order submitted:', data.id);\n  // Track submission status\n}\n\nasync function handleMedicationOrderAcknowledged(data) {\n  console.log('Medication order acknowledged:', data.id);\n  // Update acknowledgment status\n}\n\nasync function handleMedicationOrderFilled(data) {\n  console.log('Medication order filled:', data.id);\n  // Process filled order\n}\n\nasync function handleShipmentCreated(data) {\n  console.log('Shipment created:', data.shipment_id);\n  console.log('Tracking URL:', data.tracking_url);\n  // Send tracking information to patient\n}\n\nasync function handleShipmentCancelled(data) {\n  console.log('Shipment cancelled:', data.shipment_id);\n  // Handle cancellation\n}\n\nasync function handlePractitionerActivated(data) {\n  console.log('Practitioner activated:', data.practitioner_id);\n  console.log('Name:', data.practitioner_name);\n  // Update practitioner status in your system\n}\n\napp.listen(3000, () => {\n  console.log('Webhook server listening on port 3000');\n});\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"pythonflask-example","__idx":18},"children":["Python/Flask Example"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"from flask import Flask, request, jsonify\nimport os\nimport threading\n\napp = Flask(__name__)\n\n@app.route('/webhooks/novamed', methods=['POST'])\ndef webhook_handler():\n    # 1. Verify API key\n    api_key = request.headers.get('x-api-key')\n    if api_key != os.getenv('NOVAMED_WEBHOOK_SECRET'):\n        return jsonify({'error': 'Unauthorized'}), 401\n    \n    # 2. Return 200 immediately\n    payload = request.get_json()\n    \n    # 3. Process event asynchronously\n    thread = threading.Thread(target=process_webhook_event, args=(payload,))\n    thread.start()\n    \n    return jsonify({'received': True}), 200\n\ndef process_webhook_event(payload):\n    event_name = payload.get('event_name')\n    event_data = payload.get('event_data')\n    \n    handlers = {\n        'medication_order:verified': handle_medication_order_verified,\n        'medication_order:submitted': handle_medication_order_submitted,\n        'medication_order:acknowledged': handle_medication_order_acknowledged,\n        'medication_order:filled': handle_medication_order_filled,\n        'shipment:created': handle_shipment_created,\n        'shipment:cancelled': handle_shipment_cancelled,\n        'practitioner:activated': handle_practitioner_activated,\n    }\n    \n    handler = handlers.get(event_name)\n    if handler:\n        handler(event_data)\n    else:\n        print(f'Unknown event: {event_name}')\n\ndef handle_medication_order_verified(data):\n    print(f\"Medication order verified: {data.get('id')}\")\n    # Update order status\n\ndef handle_medication_order_submitted(data):\n    print(f\"Medication order submitted: {data.get('id')}\")\n    # Track submission\n\ndef handle_medication_order_acknowledged(data):\n    print(f\"Medication order acknowledged: {data.get('id')}\")\n    # Update acknowledgment status\n\ndef handle_medication_order_filled(data):\n    print(f\"Medication order filled: {data.get('id')}\")\n    # Process filled order\n\ndef handle_shipment_created(data):\n    print(f\"Shipment created: {data.get('shipment_id')}\")\n    print(f\"Tracking URL: {data.get('tracking_url')}\")\n    # Send tracking info to patient\n\ndef handle_shipment_cancelled(data):\n    print(f\"Shipment cancelled: {data.get('shipment_id')}\")\n    # Handle cancellation\n\ndef handle_practitioner_activated(data):\n    print(f\"Practitioner activated: {data.get('practitioner_id')}\")\n    print(f\"Name: {data.get('practitioner_name')}\")\n    # Update practitioner status\n\nif __name__ == '__main__':\n    app.run(port=3000)\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"managing-webhooks","__idx":19},"children":["Managing Webhooks"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"delete-a-webhook","__idx":20},"children":["Delete a Webhook"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To remove a registered webhook:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -X DELETE https://novamed-feapidev.nimbushealthcaretest.com/api/external/webhook \\\n  -H \"x-api-key: your-api-key-here\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json\" \\\n  -d '{\n    \"clinic_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"webhook_id\": \"660e8400-e29b-41d4-a716-446655440001\"\n  }'\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Response"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"success\": true,\n  \"message\": \"Webhook deleted successfully\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"security-best-practices","__idx":21},"children":["Security Best Practices"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"1-verify-the-api-key","__idx":22},"children":["1. Verify the API Key"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Always verify the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-api-key"]}," header matches your webhook secret:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"const apiKey = req.headers['x-api-key'];\nif (apiKey !== process.env.NOVAMED_WEBHOOK_SECRET) {\n  return res.status(401).json({ error: 'Unauthorized' });\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"2-use-https-only","__idx":23},"children":["2. Use HTTPS Only"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Webhook endpoints ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["must"]}," use HTTPS. NovaMed will not send webhooks to HTTP endpoints."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"3-implement-idempotency","__idx":24},"children":["3. Implement Idempotency"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Handle duplicate events gracefully using the medication order ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["id"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["event_name"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"const processedEvents = new Map();\n\nasync function isEventProcessed(id, eventName) {\n  const key = `${id}:${eventName}`;\n  return processedEvents.has(key);\n}\n\nasync function markEventProcessed(id, eventName) {\n  const key = `${id}:${eventName}`;\n  processedEvents.set(key, Date.now());\n  // Also persist to database for reliability\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"4-return-200-ok-quickly","__idx":25},"children":["4. Return 200 OK Quickly"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your endpoint should return HTTP 200 OK within ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["5 seconds"]},". Process events asynchronously:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"app.post('/webhooks/novamed', (req, res) => {\n  // Return immediately\n  res.status(200).json({ received: true });\n  \n  // Process asynchronously\n  processWebhookEvent(req.body).catch(console.error);\n});\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"error-handling--retries","__idx":26},"children":["Error Handling & Retries"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"retry-policy","__idx":27},"children":["Retry Policy"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["NovaMed will retry failed webhook deliveries:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Attempt"},"children":["Attempt"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Delay"},"children":["Delay"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["1"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Immediate"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["2"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["1 minute"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["3"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["5 minutes"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["4"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["15 minutes"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["5"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["1 hour"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After 5 failed attempts, the webhook delivery is marked as failed."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"handling-failures","__idx":28},"children":["Handling Failures"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If your endpoint returns a non-200 status code, the webhook will be retried. To prevent retries:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Return 200 OK"]}," even if processing fails"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Log errors"]}," for later processing"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Process asynchronously"]}," to avoid timeouts"]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"testing-webhooks","__idx":29},"children":["Testing Webhooks"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"local-testing-with-ngrok","__idx":30},"children":["Local Testing with ngrok"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Start your webhook server"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"node webhook-server.js\n","lang":"bash"},"children":[]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Expose with ngrok"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"ngrok http 3000\n","lang":"bash"},"children":[]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Register webhook"]}," with the ngrok URL:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -X POST https://novamed-feapidev.nimbushealthcaretest.com/api/external/webhook \\\n  -H \"x-api-key: your-api-key\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"clinic_id\": \"your-clinic-uuid\",\n    \"webhook_url\": \"https://abc123.ngrok.io/webhooks/novamed\"\n  }'\n","lang":"bash"},"children":[]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Trigger a test event"]}," by creating an order in the sandbox environment"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Monitor ngrok requests"]}," at ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["http://localhost:4040"]}]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"troubleshooting","__idx":31},"children":["Troubleshooting"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"webhooks-not-received","__idx":32},"children":["Webhooks Not Received"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ Check webhook URL is registered correctly"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ Verify endpoint is accessible (not behind firewall)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ Ensure endpoint uses HTTPS"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ Check API key is correct"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"duplicate-events","__idx":33},"children":["Duplicate Events"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ Implement idempotency checking using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["id"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["event_name"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ Store processed event keys in database"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"timeout-errors","__idx":34},"children":["Timeout Errors"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ Return 200 OK quickly (< 5 seconds)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ Process events asynchronously"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ Optimize database queries"]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"best-practices-summary","__idx":35},"children":["Best Practices Summary"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Use HTTPS"]}," - Required for webhook endpoints"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Verify API Key"]}," - Always check ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-api-key"]}," header"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Return 200 OK Quickly"]}," - Within 5 seconds"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Process Asynchronously"]}," - Don't block the response"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Implement Idempotency"]}," - Handle duplicate events using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["id"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["event_name"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Log Everything"]}," - For debugging and monitoring"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["✅ ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Handle Errors Gracefully"]}," - Don't crash on errors"]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"next-steps","__idx":36},"children":["Next Steps"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"#step-1-register-your-webhook-endpoint"},"children":["Register your webhook endpoint"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"#implementation-examples"},"children":["Implement webhook handler"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"#testing-webhooks"},"children":["Test with development environment"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Deploy to production"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"support","__idx":37},"children":["Support"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["API Support"]},": api@nimbus-os.com"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Development Environment"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://novamed-feapidev.nimbushealthcaretest.com"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Production Environment"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://feapi.novamed.care"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For more information, see the ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/api-reference"},"children":["API Reference"]},"."]}]},"headings":[{"value":"Webhook Integration Guide","id":"webhook-integration-guide","depth":1},{"value":"Overview","id":"overview","depth":2},{"value":"What Are Webhooks?","id":"what-are-webhooks","depth":3},{"value":"Benefits","id":"benefits","depth":3},{"value":"Base URLs","id":"base-urls","depth":2},{"value":"Getting Started","id":"getting-started","depth":2},{"value":"Step 1: Register Your Webhook Endpoint","id":"step-1-register-your-webhook-endpoint","depth":3},{"value":"Step 2: Set Up Your Webhook Endpoint","id":"step-2-set-up-your-webhook-endpoint","depth":3},{"value":"Webhook Events","id":"webhook-events","depth":2},{"value":"Medication Order Events","id":"medication-order-events","depth":3},{"value":"Shipment Events","id":"shipment-events","depth":3},{"value":"Practitioner Events","id":"practitioner-events","depth":3},{"value":"Webhook Payload Structure","id":"webhook-payload-structure","depth":2},{"value":"Medication Order Event Example","id":"medication-order-event-example","depth":3},{"value":"Shipment Created Event Example","id":"shipment-created-event-example","depth":3},{"value":"Practitioner Activated Event Example","id":"practitioner-activated-event-example","depth":3},{"value":"Implementation Examples","id":"implementation-examples","depth":2},{"value":"Node.js/Express Example","id":"nodejsexpress-example","depth":3},{"value":"Python/Flask Example","id":"pythonflask-example","depth":3},{"value":"Managing Webhooks","id":"managing-webhooks","depth":2},{"value":"Delete a Webhook","id":"delete-a-webhook","depth":3},{"value":"Security Best Practices","id":"security-best-practices","depth":2},{"value":"1. Verify the API Key","id":"1-verify-the-api-key","depth":3},{"value":"2. Use HTTPS Only","id":"2-use-https-only","depth":3},{"value":"3. Implement Idempotency","id":"3-implement-idempotency","depth":3},{"value":"4. Return 200 OK Quickly","id":"4-return-200-ok-quickly","depth":3},{"value":"Error Handling & Retries","id":"error-handling--retries","depth":2},{"value":"Retry Policy","id":"retry-policy","depth":3},{"value":"Handling Failures","id":"handling-failures","depth":3},{"value":"Testing Webhooks","id":"testing-webhooks","depth":2},{"value":"Local Testing with ngrok","id":"local-testing-with-ngrok","depth":3},{"value":"Troubleshooting","id":"troubleshooting","depth":2},{"value":"Webhooks Not Received","id":"webhooks-not-received","depth":3},{"value":"Duplicate Events","id":"duplicate-events","depth":3},{"value":"Timeout Errors","id":"timeout-errors","depth":3},{"value":"Best Practices Summary","id":"best-practices-summary","depth":2},{"value":"Next Steps","id":"next-steps","depth":2},{"value":"Support","id":"support","depth":2}],"frontmatter":{"title":"Webhook Integration Guide","description":"Complete guide to integrating webhooks for real-time NovaMed event notifications","seo":{"title":"Webhook Integration Guide"}},"lastModified":"2025-12-22T10:47:03.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/guides/webhooks","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}