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
| Event | Description |
|---|---|
appointment.created | A new appointment has been booked |
appointment.updated | Appointment details have changed |
appointment.cancelled | An appointment has been cancelled |
appointment.rescheduled | Appointment time or date changed |
appointment.completed | Appointment status changed to completed |
Patient Events
| Event | Description |
|---|---|
patient.created | A new patient has been registered |
patient.updated | Patient details have changed |
Document Events
| Event | Description |
|---|---|
document.created | A new document has been uploaded |
document.updated | Document 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
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier for this event |
type | string | The event type (e.g., appointment.created) |
created_at | string | ISO 8601 timestamp when event occurred |
organization_id | string | Your organization ID |
data.object | object | Current state of the resource |
data.previous | object | Previous state (for update events only) |
Setting Up Webhooks
1. Create a Webhook Endpoint
- Go to Settings > Webhooks in your Jump EHR dashboard
- Click Add Endpoint
- Enter your endpoint URL (must be HTTPS)
- Select the events you want to receive
- 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=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdThe signature format is t=<timestamp>,v1=<signature> where:
tis the Unix timestamp when the webhook was sentv1is the HMAC-SHA256 signature
Verifying Signatures
To verify a webhook signature:
- Extract the timestamp and signature from the header
- Construct the signed payload:
{timestamp}.{request_body} - Compute HMAC-SHA256 using your webhook secret
- 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', 200Additional Headers
Each webhook request includes these headers:
| Header | Description |
|---|---|
X-Webhook-Signature | HMAC-SHA256 signature for verification |
X-Webhook-Event-ID | Unique event ID for deduplication |
X-Webhook-Event-Type | Event type name |
User-Agent | JumpEHR-Webhooks/1.0 |
Content-Type | application/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:
| Attempt | Delay |
|---|---|
| 1 | 1 minute |
| 2 | 4 minutes |
| 3 | 16 minutes |
| 4 | 64 minutes (~1 hour) |
| 5 | 256 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 3000Then 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
- Learn about Authentication for API access
- Explore the API Reference for available resources
- View Patients API for patient event details