Integrating pVerify with FHIR: A Comprehensive Step-by-Step Guide

In this blog, we explore how to integrate insurance and eligibility verification with an FHIR-based system using pVerify. Specifically, we’ll be leveraging pVerify’s Eligibility Summary API to achieve this.

What is pVerify?

As described on their platform, pVerify is “The leader in all-payer real-time patient insurance eligibility verification, offering instant API and batch solutions that combine technology with AI to simplify the patient care cycle for healthcare providers in medical, dental, and vision services.” In short, pVerify simplifies insurance and eligibility verification for healthcare providers. In this blog, we’ll focus on the Eligibility Summary API of pVerify’s REST API to handle patient eligibility checks.

Why Choose pVerify?

The standout feature of pVerify is its real-time eligibility verification. pVerify provides immediate access to patient insurance information while ensuring that no sensitive data is stored on their servers. All data transmissions are encrypted, providing a secure environment even on public networks.

Setting Up pVerify

  1. Account Setup
    🔺Visit pVerify’s website and follow the account creation process.
  2. Developer Access
    🔺After setting up, access the developer portal, where you’ll obtain the Client ID and Secret.
  3. Storing Credentials
    🔺Store your Client ID and Secret in a secure secret store. In this example, we will use Medplum’s clientApplication to store these parameters.

Using pVerify’s REST API

pVerify provides a RESTful API, but our focus will be on the Eligibility Summary API.

The Eligibility Summary API has the following signature:

Request:

curl --location 'https://api.pverify.com/api/EligibilitySummary' \
--header 'Authorization: Bearer {{access-token}}' \
--header 'Client-API-Id: {{client-api-id}}' \
--header 'Content-Type: application/json' \
--data '{
    "payerCode": "00192",
    "payerName": "UHC",
    "provider": {
        "firstName": "",
        "middleName": "",
        "lastName": " test name",
        "npi": "1234567890",
        "pin":"00000"
  },

    "subscriber": {
        "firstName": "fname",
        "dob": "mm/dd/yyyy",
        "lastName": "lname",
        "memberID": "123sfadfaf"
},

 "dependent": null,
 "isSubscriberPatient": "True",
 "doS_StartDate": "02/02/2021",
 "doS_EndDate": "02/02/2021",

 "PracticeTypeCode":"3",
 "referenceId":"Pat MRN",
 "Location":"Any location Name",
 "IncludeTextResponse":"false",
 "InternalId":"",
 "CustomerID":""
}'

This returns a response signature which is too large to present here. So we will be separating the important stuff as follows.

Using pVerify’s REST API

Approach to Integration

Our Goal

  1. For FHIR to pVerify integration, we’ll need to have the initial data in FHIR’s CoverageEligibilityRequest.
  2. Then we need to convert it into pVerify API format to fetch Eligibility Summary.
  3. And finally, we’ll convert the response back into FHIR’s  CoverageEligibilityResponse.

Plan of Action

Plan-of-Action

Start Your Integration Journey Today with pVerify and FHIR for Better Care!

Our Plan Comprises Three Parts:

  1. Creating FHIR’s CoverageEligibilityRequest.
  2. Converting CER to the valid pVerify Request and Calling the summary API.
  3. Converting pVerify’s response to CoverageEligibilityResponse and storing it.

We will be using Medplum, a healthcare/FHIR-based, open-source development platform. For ease of use.

With our approach in place, let’s start with the first part:

1. CoverageEligibilityRequest

CER is a standard FHIR resource. We can take input from the user or any other source and instantiate a CER object.

We’re using a form input to pass the data to the createResource Function.

const coverageEligibilityRequest: any = await medplum.createResource({
    resourceType: "CoverageEligibilityRequest",
    status: "active",
    purpose: ["validation"],
    patient: {
      reference: `Patient/${patientId}`,
    },
    servicedPeriod: {
      start: form.miscellaneous.fromDate.toISOString().split("T")[0],
      end: form.miscellaneous.toDate.toISOString().split("T")[0],
    },
    created: new Date().toISOString().split("T")[0],
    insurer: {
      reference: `Organization/${form.payer.id}`,
    },
    insurance: [
      {
        coverage: {
          reference: `Coverage/${coverage.id}`,
        },
      },
    ],
    provider: {
      reference: `Organization/${form.provider.id}`,
    },
    item: [
      {
        category: {
           coding: [
             {
               code: form.serviceType.id,
               display: serviceTypeMap.find(
                 (x: any) => x.value == form.serviceType.id
               )?.label,
             },
           ],
          },
        },
      ],
 });

This resource and its field should be tailored to the use case. Currently, we’re using the form inputs.

Note – Eligibility Summary supports 2 types of verification, Dependent and Subscriber, where the subscriber is straightforward as the patient is the plan holder.

For the Dependent, we’ll need to hold the subscriber’s information in the RelatedPerson resource which refers to the dependent patient.

As an example, here is the structure of a parent-child relation, where the child is dependent on the parent.

CoverageEligibilityRequest

This takes care of creating and storing CER in FHIR format.

2. Call the Summary API

Now that we have the CER we’ll need to convert it to a valid format and call the API.

Conversion can be done in this way:

const subscriberR = await (coverage?.subscriber ? medplum.readReference(coverage?.subscriber) : patient);
   const isSubscriberPatient = (subscriberR == patient);
   const dob = new Date(patient.birthDate)
   const patientInfo = {
           firstName: patient.name[0].given[0],
           lastName: patient.name[0].family,
           dob: formatDate(dob),
        };

   const subscriber = isSubscriberPatient
     ? {
        ...patientInfo,
        memberID: coverage.subscriberId
     }
     : {
       memberID: coverage.subscriberId
     };

   const dosStart = new Date(eRequest.servicedPeriod.start)
   const dosEnd = new Date(eRequest.servicedPeriod.end)

   const requestJSON = {
       payerCode: insurerIdentifier.value,
       payerName: insurer.name,
       provider: {
           firstName: '',
           middleName: '',
           lastName: provider.name,
           npi: providerIdentifier.value,
           pin: ''
       },
       subscriber: subscriber,
       dependent: isSubscriberPatient ? null : {patient: patientInfo},
       doS_StartDate: formatDate(dosStart),
       doS_EndDate: formatDate(dosEnd),
       isSubscriberPatient: isSubscriberPatient,
       location: provider.address[0].city,
       practiceTypeCode: eRequest.item[0].category.coding[0].code

}

Here eRequest is the CER, and subscriber’s reference is stored in the Coverage resource. Once we have this request, we’ll need to Authenticate and call the API.

AUTH:

It’s a simple OPENAPI oauth with client credentials, it should respond with the Bearer token.

const data = new URLSearchParams()
   data.append("Client_Id", clientID);
   data.append("Client_Secret", secret);
   data.append("grant_type", "client_credentials");

   const res = await fetch(`${pverifyAPIBase}/Token`, {
     method: 'POST',
     headers: {
       'Content-Type': 'application/x-www-form-urlencoded',
       // 'Accept': 'application/json,
     },
     body: data
})

Once we have the auth token, we can call the API with a converted Response.

Summary:

const res = await fetch(`${pverifyAPIBase}/api/EligibilitySummary`, {
     method: 'POST',
     headers: {
       'Content-Type': 'application/json',
       'Authorization': 'Bearer ' + token,
       'Client-API-Id': clientID
       // 'Accept': 'application/json,
     },
     body: JSON.stringify(requestJson)

3. CoverageEligibiltyResponse

Finally, we can convert the response back into the FHIR.

While doing this some points to consider:

  • The respective coverage needs to be updated.
  • For the various coding systems, FHIR provides an exhaustive list of standards.
  • If a code is not present in the FHIR, a list can be maintained for the codes as long as it is hosted in the system URL (this is not a standard approach but can work as a stopgap).
  • The benefits of the plan need to be mapped depending on the use case.

Handling for non-EDI:

  • Not all payers support instant eligibility and can take time to respond to queries, these payers are marked as non-EDI payers and should reflect in the response’s “isPayerBackoffice” field.
  • For non-EDI payers, the request ID needs to be stored and re-fetched by calling.
async function convertToCoverageResponse(res:any, eRequest:any, medplum:MedplumClient):Promise<any> {
   const identifier = [{
       system: codingSystemMap.pverify,
       value: String(res.RequestID),
   }
   ];

const efDate = res.PlanCoverageSummary?.EffectiveDate?.replaceAll(' ', '');
const exDate = res.PlanCoverageSummary?.ExpiryDateDate?.replaceAll(' ', '');


const startDate = efDate ? new Date(efDate).toISOString() : null;
const endDate = exDate ? new Date(exDate).toISOString() : null;


const coverage = await medplum.readReference(eRequest.insurance[0].coverage)
const updatedCoverage = await medplum.updateResource<any>({
    ...coverage,
    resourceType: 'Coverage',
    id: coverage.id,
    status: res.PlanCoverageSummary.Status == 'Active' ? 'active' : 'cancelled',
    type: {
        coding: [{
            system: codingSystemMap.actCode,
            code: "EHCPOL",
            display: "extended healthcare"
        }]
    },
    period: {
        start: startDate??undefined,
        end: endDate??undefined
    },
    class: [{
        type: {
            text: res.PlanCoverageSummary?.PolicyType??''
        },
        name: res.PlanCoverageSummary?.PlanName??'',
        value: res.PlanCoverageSummary?.PlanNumber??'--'
    }],
    network: res.PlanCoverageSummary?.PlanNetworkName??undefined

});

const CoverageEligibilityResponse = await medplum.createResource({
    resourceType: 'CoverageEligibilityResponse',
    identifier: identifier,
    status: res.PlanCoverageSummary.Status == 'Active' ? 'active' : 'cancelled',
    purpose: ['validation', 'benefits'],
    patient: eRequest.patient,
    servicedPeriod: eRequest.servicedPeriod,
    created: new Date().toISOString(),
    request: createReference(eRequest),
    outcome: res.IsPayerBackOffice
       ? 'queued'
       : res.ProcessedWithError
           ? 'error'
           : 'complete',
    insurer: eRequest.insurer,
    insurance: generateBenefitsList(res.ServiceDetails, updatedCoverage, res.PlanCoverageSummary.Status == 'Active'),
    extension: generateSummaryList(res)
  });

  return CoverageEligibilityResponse;

}

Finally, the entire flow should look like this:

GetEligibilitySummary

coma

Conclusion

Integrating pVerify with an FHIR-based system enables healthcare providers to efficiently verify insurance eligibility. This guide outlines how to convert FHIR’s CoverageEligibilityRequest into a format suitable for pVerify’s Eligibility Summary API and then transform the response back into FHIR’s CoverageEligibilityResponse. This streamlined approach ensures real-time access to patient insurance information while maintaining data security through encrypted transmissions.

This integration significantly simplifies eligibility checks and enhances operational workflows in healthcare. As technology advances, such solutions will be vital for providers to deliver timely and accurate care amid the complexities of insurance verification. By following the outlined steps, organizations can effectively implement this integration and enjoy its many benefits.

Keep Reading

Keep Reading

  • Service
  • Career
  • Let's create something together!

  • We’re looking for the best. Are you in?