API Documentation is in beta. Report issues to developers@jump.health
Webhooks

Webhooks

Webhooks allow you to receive real-time notifications when events occur in Jump EHR. Instead of polling the API for changes, webhooks push data to your server as events happen.

Overview

When you configure a webhook endpoint, Jump EHR will send HTTP POST requests to your URL whenever subscribed events occur. This enables real-time integrations without consuming your API rate limit.

Event Types

Appointment Events

EventDescription
appointment.createdA new appointment has been booked
appointment.updatedAppointment details have changed
appointment.cancelledAn appointment has been cancelled
appointment.rescheduledAppointment time or date changed
appointment.completedAppointment status changed to completed

Patient Events

EventDescription
patient.createdA new patient has been registered
patient.updatedPatient details have changed

Document Events

EventDescription
document.createdA new document has been uploaded
document.updatedDocument metadata has changed

Payload Structure

All webhook payloads follow a consistent structure:

{
  "id": "evt_abc123",
  "type": "appointment.created",
  "created_at": "2025-01-15T10:30:00Z",
  "organization_id": "org_xyz789",
  "data": {
    "object": {
      "id": "apt_def456",
      "patient_id": "pat_ghi789",
      "clinician_profile_id": "cli_jkl012",
      "appointment_type_id": "type_mno345",
      "title": "Follow-up Consultation",
      "start_time": "2025-01-20T14:00:00Z",
      "end_time": "2025-01-20T14:30:00Z",
      "status": "confirmed",
      "location_id": "loc_pqr678",
      "is_remote": false,
      "attendee_name": "Sarah Johnson",
      "attendee_email": "sarah@example.com",
      "created_at": "2025-01-15T10:30:00Z",
      "updated_at": "2025-01-15T10:30:00Z"
    },
    "previous": null
  }
}

Payload Fields

FieldTypeDescription
idstringUnique identifier for this event
typestringThe event type (e.g., appointment.created)
created_atstringISO 8601 timestamp when event occurred
organization_idstringYour organization ID
data.objectobjectCurrent state of the resource
data.previousobjectPrevious state (for update events only)

Setting Up Webhooks

1. Create a Webhook Endpoint

  1. Go to Settings > Webhooks in your Jump EHR dashboard
  2. Click Add Endpoint
  3. Enter your endpoint URL (must be HTTPS)
  4. Select the events you want to receive
  5. Click Create
⚠️

Your webhook secret is displayed only once when creating the endpoint. Copy and store it securely.

2. Configure Your Server

Your server must:

  • Accept POST requests
  • Respond with a 2xx status code within 30 seconds
  • Verify the webhook signature (recommended)

URL Requirements

  • Must use HTTPS (HTTP not allowed in production)
  • Cannot point to internal/private IP addresses (127.x, 10.x, 172.16-31.x, 192.168.x)
  • Must be publicly accessible

Security

Signature Verification

Every webhook request includes a signature header for verification:

X-Webhook-Signature: t=1705312200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

The signature format is t=<timestamp>,v1=<signature> where:

  • t is the Unix timestamp when the webhook was sent
  • v1 is the HMAC-SHA256 signature

Verifying Signatures

To verify a webhook signature:

  1. Extract the timestamp and signature from the header
  2. Construct the signed payload: {timestamp}.{request_body}
  3. Compute HMAC-SHA256 using your webhook secret
  4. Compare with the provided signature

Node.js Example

const crypto = require('crypto');
 
function verifyWebhookSignature(payload, signature, secret) {
  const [tPart, v1Part] = signature.split(',');
  const timestamp = tPart.split('=')[1];
  const providedSignature = v1Part.split('=')[1];
 
  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
 
  // Use timing-safe comparison
  return crypto.timingSafeEqual(
    Buffer.from(providedSignature),
    Buffer.from(expectedSignature)
  );
}
 
// Express.js example
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const payload = req.body.toString();
 
  if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
 
  const event = JSON.parse(payload);
  console.log('Received event:', event.type);
 
  // Process the event
  switch (event.type) {
    case 'appointment.created':
      handleAppointmentCreated(event.data.object);
      break;
    case 'patient.updated':
      handlePatientUpdated(event.data.object, event.data.previous);
      break;
  }
 
  res.status(200).send('OK');
});

Python Example

import hmac
import hashlib
 
def verify_webhook_signature(payload: str, signature: str, secret: str) -> bool:
    parts = dict(part.split('=') for part in signature.split(','))
    timestamp = parts['t']
    provided_signature = parts['v1']
 
    signed_payload = f"{timestamp}.{payload}"
    expected_signature = hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()
 
    return hmac.compare_digest(provided_signature, expected_signature)
 
# Flask example
@app.route('/webhooks', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Webhook-Signature')
    payload = request.get_data(as_text=True)
 
    if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
        return 'Invalid signature', 401
 
    event = request.get_json()
    print(f"Received event: {event['type']}")
 
    # Process the event
    if event['type'] == 'appointment.created':
        handle_appointment_created(event['data']['object'])
 
    return 'OK', 200

Additional Headers

Each webhook request includes these headers:

HeaderDescription
X-Webhook-SignatureHMAC-SHA256 signature for verification
X-Webhook-Event-IDUnique event ID for deduplication
X-Webhook-Event-TypeEvent type name
User-AgentJumpEHR-Webhooks/1.0
Content-Typeapplication/json

Delivery & Retries

Delivery Behavior

  • Webhooks are delivered within seconds of the event occurring
  • Each delivery attempt has a 30-second timeout
  • Your endpoint must respond with a 2xx status code to confirm receipt

Retry Schedule

If delivery fails, Jump EHR retries with exponential backoff:

AttemptDelay
11 minute
24 minutes
316 minutes
464 minutes (~1 hour)
5256 minutes (~4 hours)

After 5 failed attempts, the delivery is marked as failed and not retried.

Success Criteria

A delivery is considered successful when your endpoint:

  • Responds with HTTP status 200-299
  • Responds within 30 seconds

Return a 2xx response quickly, then process the webhook asynchronously. This prevents timeouts for long-running operations.

Best Practices

1. Respond Quickly

Acknowledge the webhook immediately and process asynchronously:

app.post('/webhooks', (req, res) => {
  // Respond immediately
  res.status(200).send('OK');
 
  // Process asynchronously
  processWebhookAsync(req.body).catch(console.error);
});

2. Handle Duplicates

Use the X-Webhook-Event-ID header to detect and skip duplicate deliveries:

const processedEvents = new Set();
 
function handleWebhook(event, eventId) {
  if (processedEvents.has(eventId)) {
    console.log('Duplicate event, skipping');
    return;
  }
 
  processedEvents.add(eventId);
  // Process event...
}

3. Verify Signatures

Always verify webhook signatures in production to ensure requests are from Jump EHR.

4. Log Webhook Events

Keep logs of received webhooks for debugging and auditing:

function logWebhook(event, status) {
  console.log({
    timestamp: new Date().toISOString(),
    event_id: event.id,
    event_type: event.type,
    status: status
  });
}

Testing Webhooks

Test Endpoint

Use the Test button in the webhook settings to send a test event:

{
  "id": "evt_test_123",
  "type": "test.ping",
  "created_at": "2025-01-15T10:30:00Z",
  "organization_id": "org_xyz789",
  "data": {
    "object": {
      "message": "This is a test webhook"
    },
    "previous": null
  }
}

Local Development

For local development, use a tunneling service like ngrok to expose your local server:

ngrok http 3000

Then use the ngrok URL as your webhook endpoint during development.

Viewing Delivery History

View webhook delivery history in Settings > Webhooks > select your endpoint > Delivery Log.

The log shows:

  • Event type and ID
  • Delivery status (delivered, failed, pending)
  • Response status code
  • Number of attempts
  • Timestamps

Next Steps