Notifications

POST /Notifications is the self-service callback on this API. It is not the agent webhook pipeline (topics such as updateApplicationStatus). That system is documented under Webhooks.

Registration stores one HTTPS URL for the authenticated BOX user (negotiator). BOX POSTs a small JSON body when a subscribed referencing event happens for an applicant that user owns. You can still poll reports if you do not use notifications.

Registration replaces any previous URL, key, and event list for that user. It is not per application, and it is not the agent WebhookAddress.

sequenceDiagram
  autonumber
  actor CRM
  participant API as Lettings Hub
  CRM->>API: POST /Notifications
  API-->>CRM: 200
  Note over API: referencing completes
  API->>CRM: POST JSON plus x-webhook-signature
  CRM->>API: GET report PDF
  API-->>CRM: 200, PDF
        

Register once per BOX user, then fetch the PDF when a report event arrives.

Register

{
  "Uri": "https://crm.example/hooks/lettings-hub",
  "Key": "shared-secret",
  "Notifications": [5, 11, 12, 13]
}

Example body. Uri is required. Empty Notifications returns 406.

Code Event
5Form submitted
11Reference processed (one income reference finished)
12Interim report done
13Final report done

Inbound POST

BOX sends Content-Type: application/json. The body is:

{
  "ReferenceNo": "s0013524",
  "Notification": 13,
  "Message": "Elite Report created"
}
Field Meaning
ReferenceNo Applicant reference (tenant or guarantor), not the application reference.
Notification Event code as a number (5, 11, 12, or 13).
Message English description. Useful to log; do not parse it.
You cannot look up a case from ReferenceNo alone. Store applicant reference → application reference when you create the tenant. See Identifiers.

Signature

If you sent Key at registration, BOX adds header x-webhook-signature: hex-encoded HMAC-SHA256 of the raw JSON body, keyed with Key. Both key and body are hashed as ASCII bytes. Compare in constant time. If you omitted Key, there is no signature header.

import hmac, hashlib

def valid(body: bytes, key: str, header: str) -> bool:
    expected = hmac.new(key.encode("ascii"), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header)

Verify against the raw request body, not a re-serialised object.

After a report event

12 and 13 mean a PDF is available. Fetch it with GET /Applications/{ApplicationRef}/{ApplicantRef}/Reports/{ReportType}. The notification does not include the file.

If your endpoint fails, BOX logs the error and does not retry. Make the handler idempotent; event 11 in particular can fire more than once (once per income reference).