Subscriptions API
Snapdocs Connect's eClose and Quality Control products broadcast events through one shared Subscriptions API. Rather than polling for changes against HttpGet endpoints, you register an endpoint you host; Snapdocs then creates HttpPost calls when an event takes place that your subscription signed up to know about. Your endpoint will send a 200 upon receipt of a valid shaped event, and manage any necessary handling, processing, and follow-up API calls that make sense for the workflow of that event.
Setup is two steps:
- Stand up an HTTPS endpoint in your system that can receive HttpPOST requests, which can trigger the appropriate work.
- Create a subscription for the events you want, using your newly created endpoint's URL.
Your OAuth scopes determine which events you can subscribe to. Each product documents its own event catalog: eClose Events Catalog, Quality Control Events Catalog. Notary Connect and eVault deliver webhooks through their own systems; see How webhooks work.
Manage subscriptions
The webhook event
Events arrive as a POST with Content-Type: application/json. The envelope carries a unique event ID, the identifier of the record the event is about, the event name, a Unix timestamp, and a payload object with event-specific detail. An eClose example below for when a preview package is available for a given borrower:
{ "event_id": "572f592a-fbec-49d9-a28a-88d8e38175be", "closing_uuid": "d679e2ad-278d-e547-9756-84639ba3865b", "event_name": "borrower.preview_available", "created_at": 1618936005, "payload": { "external_identifiers": [{ "external_system": "other_los", "external_type": "file_number", "value": "1234" }] } }
external_identifiers may have 0 or more records, echoing the identifiers your system supplied when it created the record. Snapdocs includes the events you pass in, expecting your system can better match to its underlying record via system ID.Events never contain PII. Names, addresses, and document contents require an authenticated follow-up call; Webhook security covers this and the rest of the webhook security model.
Handle the requests
- Subscribe narrowly. Configure your endpoint to receive only the event types your integration acts on. Listening for everything puts undue strain on your server.
- Handle duplicates. Endpoints occasionally receive the same event more than once. Make your event processing idempotent.
- Verify the sender. Validate the HMAC signature on every delivery, ensuring the source that hits your endpoint is the Snapdocs webhook subscription.
- Don't assume ordering. Events are not guaranteed to arrive in the order they occurred. Use
created_atto sequence them.
Respond, then process
Respond 200 as soon as the webhook arrives, before processing the event and regardless of whether processing later succeeds. In production, most listeners push the message onto a queue and return immediately.
HTTP/1.1 200 OK Content-Type: application/json { "status": "OK", "code": 200, "message": "webhook received successfully" }
If Snapdocs doesn't receive a 200, it re-sends the notification up to 13 times over 24 hours from the first attempt in an exponential backoff pattern, with increasing gaps between attempts.
Example listener
A minimal AWS Lambda listener, receiving events and routing one of them to a handler:
import json import os import logging logger = logging.getLogger() LOG_LEVEL = os.environ.get('LOG_LEVEL', 'WARNING').upper() logger.setLevel(level=LOG_LEVEL) success_code = 200 success_body = { "status": "OK", "code": success_code, "message": "webhook received successfully" } def download_scanback_document(document_uuid): logger.info("downloading scanback document %s", document_uuid) pass def process_snapdocs_event(event_body): try: # check event body logger.debug("%s %s %s", event_body.get('event_id'), event_body.get('closing_uuid'), event_body.get('event_name')) logger.debug("lender system attributes %s", event_body.get("payload")) # typically we put the message into a message broker to be processed downstream if event_body.get('event_name') == 'document.created': if event_body.get('document_type') == 'scanback_documents': download_scanback_document(event_body.get('document_uuid')) except Exception as e: logger.error(e) return 500, { "status": "Failed to process event", "code": 500, "message": f"webhook received but could not process {str(e)}" } return success_code, success_body def handle(event, context): code = success_code response_body = success_body try: if event['body']: body = json.loads(event['body']) code, response_body = process_snapdocs_event(body) else: raise "no data found in the post body" except Exception as e: code = 400 response_body = { "status": "Bad Request", "code": code, "message": f"failed to parse the webhook, {str(e)}" } finally: return {"statusCode": code, "body": json.dumps(response_body)}
For a local listener to test against, see Test your integration.