Transaction Fulfillment
Once a partner applications back-end receives a transaction initiation webhook
notification and retrieves the transaction request information, it can begin the process of fulfilling the request and updating the transaction response.
ZipRight
handles the following webhook
notifications from the EPC platform:
- New transaction initiated by a lender from the ZipRight user-interface
- Transaction response delivered by ZipRight's back-end successfully processed by EPC
Transaction request processing
When a lender user on Web Version of Encompass initiates a new transaction via the ZipRight user-interface, the EPC platform prepares the request and pushes a webhook notification to the ZipRight back-end. This webhook has the resource type urn:elli:epc:transaction
- and event type created
.
Step 1: Verify the webhook
First of all, the ZipRight Backend verifies the incoming webhook's signature. Verification requires extracting the Elli-Signature
request header and comparing it against the SHA256
HMAC
signature generated from the webhook body and shared secret webhook signingkey
specified in the product configuration.
The following snippet demonstrates the signature validation in the ZipRight backend:
ellie_signature = '{}'.format(connexion.request.headers.get('Elli-Signature'))
req_body_str = json.dumps(body)
message = bytes(req_body_str, 'ascii')
secret = bytes(signingkey, 'ascii')
hmac_digest = hmac.new(secret, message, digestmod=hashlib.sha256).digest()
mesg_signature_str = str(base64.b64encode(hmac_digest), 'ascii')
if mesg_signature_str == ellie_signature:
# Accept and process the request
...
return 'Accepted', 202
else
return 'Unauthorized', 401
Step 2: Identify the transaction event type
ZipRight uses the webhook eventType
and resourceType
to determine whether the webhook notification is communicating new transaction request creation, or a transaction response fulfillment event.
if event_type == 'created' and resource_type == 'urn:elli:epc:transaction':
# Initiate request processing thread
...
elif event_type == 'created' and resource_type == 'urn:elli:epc:transaction:event'
# Initiate response fulfillment reconciliation thread
...
Step 3: Process the request
If it is a new request, ZipRight offloads the request processing to a new thread and returns the HTTP 202 (Accepted)
status to EPC. The following snippet demonstrates the transaction type identification and processing:
if event_type == 'created' and resource_type == 'urn:elli:epc:transaction':
# Update dummy request logs with initial entry for the request
request_tracker_obj = dict()
request_tracker_obj['status'] = 'VALIDATION_REQUESTED'
request_tracker_obj['resource_id'] = None
app.request_tracker[transaction_id] = request_tracker_obj
# Kick off request processing thread
access_token = app.token_manager.get_token()
request_processor = threading.Thread(target=verify_with_usps, args=(transaction_id))
request_processor.start()
First, retrieve the transaction request:
ZipRight's business logic involves calling the USPS public API to look up and verify the city and state specified for the subject property as received in the request loan data. To retrieve the subject property data required to perform this validation, ZipRight's back-end uses the resourceRef
received in the webhook to call back into the transactions REST API and retrieve the transaction request. The following snippet demonstrates the request retrieval operation:
def get_property_data(transaction_id) -> dict:
"""Retrieve loan subject property data from transaction request"""
# Prepare API request
access_token = app.token_manager.get_token()
url = EPC_BASE_URL + EPC_TRANSACTIONS_URL + transaction_id
headers = {
"Authorization": "Bearer" + " " + access_token
}
# Retrieve transaction request
response = get(url=url, headers=headers)
# Raise exception for API failures
if response.status_code != codes.ok:
response.raise_for_status()
# Extract and return subject property information
response_obj = response.json()
epc_request_object = response_obj.get("request", None)
loan_object = epc_request_object.get("loan", None)
property_object = loan_object.get("property", None)
return property_object
Second, verify the ZIP code and update transaction response:
Once ZipRight has the subject property ZIP code, it calls the USPS Web Service and gets the associated city and state. Upon a successful USPS service response, ZipRight delivers its completed
response for the transaction - with the USPS verified subject property city and state information - by invoking the REST API's PATCH /partner/v2/transactions/response
endpoint. This is a multi-step process for ZipRight:
def update_response(status, property_information, transaction_id) -> dict:
# Prepare response update
access_token = app.token_manager.get_token()
url = EPC_BASE_URL + EPC_TRANSACTIONS_URL + transaction_id + "/response"
headers = {
"Authorization": "Bearer" + " " + access_token
}
content_type = MIMETYPES.get("JSON")
payload = {
"status": status,
"partnerStatus": "ZIP Code Verification Completed",
"referenceNumber": str(uuid.uuid4()),
"respondingParty": {
"name": "ZipRight Corp.",
"address": "P.O. BOX 509124, SUITE 300",
"city": "San Diego",
"state": "CA",
"postalCode": "92150",
"pointOfContact": {
"name": "John Doe",
"role": "Manager",
"phone": "8009864343",
"email": "[email protected]"
}
},
"loanFormat": LOAN_FORMAT,
"loan": {
"property": propery_information
}
}
# Deliver response update
response = patch(url=url, content_type=content_type, headers=headers, body=payload)
# Raise exception for API failures
if response.status_code != codes.ok:
response.raise_for_status()
return response
ZipRight uses the received city and state to generate a PDF document and upload it to EPC as an attachment. Uploading the attachment involves three steps:
- ZipRight sends EPC a request to upload an attachment with its type. In response, EPC sends ZipRight a URL, authorization token, and resource id.
- ZipRight streams the PDF file to the URL it received in the previous step
- After a successful upload, ZipRight delivers its
completed
response for the transaction, providing the resource id of the attachment along with the verified property data
The code snippets below demonstrate the three steps of the upload process:
def _prepare_upload_file(file_name, transaction_id):
"""Utility function for staging transaction response attachment"""
# Prepare response resource request
access_token = app.token_manager.get_token()
url = EPC_BASE_URL + EPC_TRANSACTIONS_URL + transaction_id + "/response/resources"
headers = {
"Authorization": "Bearer" + " " + access_token,
}
content_type = MIMETYPES.get("JSON")
payload = [
{
"name": file_name,
"mimeType": "application/pdf"
}
]
# Create response resource
response = post(url=url, content_type=content_type, headers=headers, body=payload)
# Raise exception for API failures
if response.status_code != codes.ok:
response.raise_for_status()
return response
...
if prepare_upload_rsp.status_code == 200:
prepare_upload_rsp_obj = prepare_upload_rsp.json()
resource_id = prepare_upload_rsp_obj[0].get("id", None)
partner_resource_location = prepare_upload_rsp_obj[0].get("location", None)
partner_resource_authorization_header = prepare_upload_rsp_obj[0].get("authorization", None)
partner_resource_mimeType = prepare_upload_rsp_obj[0].get("mimeType", None)
partner_file_name = prepare_upload_rsp_obj[0].get("name", None)
# Prepare upload request
url = partner_resource_location
headers = {
"Authorization": partner_resource_authorization_header,
"Content-Type": partner_resource_mimeType
}
# Upload file
response = put_file(url, file_name, headers=headers)
# Raise exception for API failures
if response.status_code != codes.ok:
response.raise_for_status()
...
...
# Prepare transaction update request
access_token = app.token_manager.get_token()
url = EPC_BASE_URL + EPC_TRANSACTIONS_URL + transaction_id + "/response"
headers = {
"Authorization": "Bearer" + " " + access_token
}
content_type = MIMETYPES.get("JSON")
payload = {
# Response status and loan data
"status": status,
"partnerStatus": "Inspection Scheduled",
"referenceNumber": "CXV90345",
"respondingParty": {
"name": "AddressVerify Corp.",
"address": "P.O. BOX 509124, SUITE 300",
"city": "SAN DIEGO",
"state": "CA",
"postalCode": "92150",
"pointOfContact": {
"name": "John Doe",
"role": "Manager",
"phone": "8009864343",
"email": "[email protected]"
}
},
"loanFormat": LOAN_FORMAT,
"loan": {
"property": propery_information
},
# Reference uploaded resource (file attachment)
"resources": [
{
"id":resource_id,
"name": file_name,
"mimeType":mimeType
}
]
}
# Update transaction response
response = patch(url=url, content_type=content_type, headers=headers, body=payload)
# Raise exception for API failures
if response.status_code != codes.ok:
response.raise_for_status()
...
Response fulfillment callback
EPC notifies integrations of transaction response processing success or errors via callback transaction events, enabling Partners to update and synchronize their internal management of orders against transaction processing success/errors in the EPC platform, and drive the appropriate workflows in their application. Once ZipRight successfully delivers its completed
response to the EPC platform - it waits for a callback webhook notification for a new urn:elli:epc:transaction:event
resource being created
:
{
"eventTime" : "2020-05-02T11:08:52Z",
"eventType" : "created",
"meta" : {
"resourceType" : "urn:elli:epc:transaction:event",
"resourceId" : "{{TRANSACTION_EVENT_ID}}",
"instanceId" : "{{PRODUCT_NAME}}",
"resourceRef" : "https://api.elliemae.com/partner/v2/transactions/{{TRANSACTION_ID}}/events/{{TRANSACTION_EVENT_ID}}"
}
}
Once ZipRight receives this webhook - it retrieves the specific transaction event (resourceRef
) and verifies that the transaction event is of type urn:elli:epc:transaction:response:processed
- indicating response fulfillment. Once this is confirmed, ZipRight logs completion of the transaction in its dummy request tracker - and the transaction fulfillment process is completed 🎉
elif event_type == 'created' and resource_type == 'urn:elli:epc:transaction:event':
# Call back into transaction event - and verify it is of type urn:elli:epc:transaction:response:processed
...
# Update dummy request tracker with completed status
req_tracker = app.request_tracker.get(transaction_id, None)
if req_tracker is not None:
req_tracker['status'] = "REQUEST_COMPLETED"
You can read more about the response processing callback events events emitted by the EPC platform in the Deeper Dive section.
Updated over 1 year ago