Webhooks
Receive real-time notifications when events occur in your Flexpa integration.
Flexpa sends HTTPS POST requests to your endpoint instead of requiring you to poll for changes.
#Available events
- sync_completed: Patient data synchronization is complete and ready to query via the FHIR API
#Setup
Contact support@flexpa.com with your webhook endpoint URL(s) and their associated mode (test or live).
The Flexpa team will register them and send you webhook secrets via secure link.
Webhooks are configured separately for test and live modes. A webhook will only receive events matching its configured mode.
Self-service management via API is coming soon.
Webhook payload
{
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"event": "sync_completed",
"version": 1,
"timestamp": 1704067200000,
"data": {
"mode": "live",
"external_id": "user_123",
"patient_authorization_id": "7c3e9f2a-4b8d-4e1f-9a3c-5d7e8f9a1b2c"
}
}
Request headers
Content-Type: application/json
User-Agent: Flexpa-Webhook/1.0
X-Flexpa-Signature: t=1704067200,v1=5f7d8a...
#Verifying signatures
Every webhook request includes an X-Flexpa-Signature header that contains a timestamp and signature.
You should verify this signature to ensure the request came from Flexpa.
The signature format is: t={timestamp},v1={signature}
#Verification steps
- Extract the timestamp and signature from the header
- Construct the signed payload as:
{timestamp}.{request_body}
- Compute an HMAC-SHA256 hash using your webhook secret
- Compare the computed signature with the received signature
- Verify the timestamp is recent (within 5 minutes) to prevent replay attacks
Always use a constant-time comparison function when comparing signatures to prevent timing attacks.
Signature verification
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
// Parse signature header
const [tPart, v1Part] = signature.split(',');
const timestamp = parseInt(tPart.split('=')[1]);
const receivedSignature = v1Part.split('=')[1];
// Verify timestamp is recent (within 5 minutes)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > 300) {
return false;
}
// Compute expected signature
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Use constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(receivedSignature)
);
}
#Handling events
#sync_completed
Triggered when Flexpa has finished synchronizing a patient's data from their health plan.
This event fires after each data synchronization, including the initial sync when a patient connects and subsequent refreshes for MULTIPLE usage authorizations.
Payload fields
- event_id
Unique identifier for this event (UUID v4).
Store this to implement idempotent processing, as Flexpa may send the same event multiple times due to retries.
- event
Always "sync_completed".
- version
Payload schema version (currently 1).
- timestamp
Unix timestamp in milliseconds when the event occurred.
- data.mode
The mode for this authorization: "test" or "live". This will always match the mode of the webhook that received the event.
- data.external_id
Your application's user identifier for this patient that was passed through the Consent SDK.
- data.patient_authorization_id
The patient authorization ID that completed syncing.
This is the sub claim from the access token returned by the exchange endpoint.
Use this ID to query the patient's FHIR resources.
Handling sync_completed
import express from 'express';
app.post('/webhook', express.json(), async (req, res) => {
const signature = req.headers['x-flexpa-signature'];
const payload = JSON.stringify(req.body);
// Verify signature
if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const { event_id, event, data } = req.body;
// Check for duplicate events
if (await isDuplicateEvent(event_id)) {
return res.status(200).send('Already processed');
}
if (event === 'sync_completed') {
const { patient_authorization_id, external_id, mode } = data;
// Queue for async processing - don't block the webhook response
// Fetch FHIR data and process in a background job
await queueDataSync({
patientAuthorizationId: patient_authorization_id,
externalId: external_id,
mode: mode,
eventId: event_id,
});
}
// Acknowledge receipt immediately
res.status(200).send('OK');
});
#Retry behavior
If your webhook endpoint returns a non-2xx status code or fails to respond within 30 seconds, Flexpa will automatically retry the delivery using exponential backoff.
Webhooks are retried up to 5 times with increasing delays between attempts (2 minutes, 4 minutes, 8 minutes, 16 minutes).
After 5 failed attempts, Flexpa stops retrying and marks the delivery as failed.
Implement idempotent webhook handling using the event_id field to safely process duplicate events from retries.
#Best practices
- Return quickly: Acknowledge receipt immediately with a 2xx status code and process the webhook asynchronously
- Be idempotent: Use
event_id to detect and skip duplicate events
- Verify signatures: Always validate the
X-Flexpa-Signature header before processing
- Handle errors gracefully: Return 2xx for successfully received events, even if your internal processing fails
- Log events: Keep a record of received webhooks for debugging and auditing
#FAQ
#Why isn't my webhook being received?
Verify your endpoint URL is correct and publicly accessible.
Check that your server is running and that firewall rules aren't blocking incoming requests from Flexpa.
Ensure your endpoint returns a 2xx status code.
#Why is signature verification failing?
Ensure you're using the correct webhook secret.
Verify you're constructing the signed payload correctly as {timestamp}.{request_body}.
Make sure you're using the raw request body string, not parsed JSON.
#How do I test webhooks in test mode?
When you trigger a sync in test mode, Flexpa will send webhook notifications to your test mode webhook endpoint.
Make sure you've configured a separate webhook for test mode, as webhooks registered for live mode will not receive test mode events.
Use the test credentials and flows described in the test mode documentation to trigger sync events.
#What happens if my endpoint is down during a webhook delivery?
Flexpa will automatically retry the delivery up to 5 times using exponential backoff.
If all retries fail, the delivery is marked as failed.
#Need help?
If you're experiencing issues with webhooks, contact support with your application ID, an example webhook payload, and the X-Request-Id header from a failed delivery.