Webhooks
Agent webhooks are outbound POSTs from BOX to URLs stored on the
agent (WebhookAddress). They carry a
topic (or a KYC / report payload without one) and are
retried if delivery fails. They are not registered through this API.
Subscribe
Give Lettings Hub an HTTPS URL that accepts
POST with Content-Type: application/json.
Optionally a static header name and value (for example
X-API-Key); BOX sends it on every delivery to every URL.
BOX does not sign deliveries to partner URLs, so use that header and
HTTPS to authenticate them. Every request carries
User-Agent: Homespun-Webhook-Client/1.0.
Several URLs can be stored comma-separated. Each attempt POSTs the same body to every URL. Delivery counts as successful if any URL returns 2xx; the others are still called.
Ask your account manager to enable WebhookEnabled and set
the address.
What to return
Return any 2xx as soon as you have accepted the body. BOX does not read the response body. Process heavy work asynchronously. Ignore unknown fields and unknown topics.
A 4xx other than 408 or 429 tells BOX the delivery is rejected for good: it stops retrying that payload and marks it dead. Anything else (5xx, 408, 429, a timeout, a dropped connection) is retried as below. So return 4xx only when you never want that payload again.
Delivery
The first send is attempted when the event is raised, with a 10-second timeout. If that fails, the payload is stored and a background sender retries it about once a minute, fifteen attempts in all; each of those attempts itself retries up to three times on 408, 429, 502, 503, 504 or a dropped connection. After the fifteenth failure the payload is marked dead and logged. A permanent 4xx (above) marks it dead at once.
Duplicates can occur. Deduplicate on topic plus
applicationRef and any stable ids inside data.
For KYC use KYCid; for reports use URL or
EntryType plus applicant ids.
Envelope
Most events include topic, applicationRef
(the tenancy / application reference), optional data, and
metadata: EntryType, CreationDate,
ApplicationId, ApplicantId,
ApplicationExternalReference,
ApplicantExternalReference.
KYC status has no topic — look for KYCid
and EntryType Status Update KYC. Report
available sets EntryType Report Available
plus ReportType and URL; because that
payload is built on the applicant-status type, you will usually also
see topic updateApplicant. Key off
EntryType or URL, not topic alone.
Topics
Every topic BOX sends today. Ignore any topic you do not handle.
| Topic | When |
|---|---|
createNewTenancy | A tenancy is created |
addApplicant | An applicant is added |
updateApplicant | An applicant’s status changes (also carried by Report available) |
applicantReopened | A completed applicant is reopened for more referencing work |
updateApplicationStatus | The tenancy step changes |
uploadReapitDocument | A document is ready (base64 fileData or a presignedUrl) |
prescribedInformationCompleted | Prescribed information (deposit protection) is submitted |
tenancyFinalised | The tenancy agreement is fully signed and the tenancy finalised |
tenancyStartDateChanged | Start date changes (yyyy-MM-dd) |
addNote | A note or audit-trail line is written against an applicant or the tenancy |
rentIncreaseNoticeIssued | The agent issues a rent-increase notice |
rentIncreaseNoticeWithdrawn | An issued notice is withdrawn |
rentIncreaseWithdrawalFinalised | The withdrawal-notice document is finalised |
rentIncreaseApplied | The increase takes effect on its start date |
rentIncreaseTribunalReferral | A tenant refers the increase to the tribunal |
rentIncreaseTribunalReferralWithdrawn | The referral is withdrawn |
rentIncreaseTribunalDecisionConfirmed | The agent records the tribunal’s decision |
agreedRentConfirmationIssued | The agent issues an agreed-rent confirmation after a rent review |
agreedRentConfirmationFinalised | That confirmation document is finalised |
updateApplicationStatus
{
"topic": "updateApplicationStatus",
"applicationRef": "0013524",
"applicationStatus": 4,
"isPrescribedInformationRequired": true,
"isPrescribedInformationCompleted": false,
"isTenancyFinalised": false,
"howWillSignApplicants": 1,
"howWillSignAgentLandlord": 1,
"EntryType": "Update Application Status"
}
applicationStatus is the tenancy step:
| Value | Step |
|---|---|
| 1 | Referencing in progress |
| 2 | Referencing complete |
| 3 | Tenants confirming preferences |
| 4 | Awaiting tenancy agreement |
| 5 | Awaiting tenant signing |
| 6 | Awaiting landlord signing |
| 7 | Awaiting agent signing |
| 8 | Awaiting agent to confirm inventory |
| 9 | Awaiting move-in meter readings |
| 10 | Awaiting move-out meter readings |
| 11 | Complete |
| 12 | Tenancy archived |
updateApplicant
data.applicantRef is the applicant reference.
applicationStatus here is not the table
above: it is 0 while the application is still starting,
otherwise 1. When the applicant is completed, a full
Applicant object may be included.
{
"topic": "updateApplicant",
"applicationRef": "0013524",
"applicationStatus": 1,
"Status": "References in Progress",
"data": {
"applicantRef": "s0013524",
"applicantStatus": "References in Progress",
"reportType": 3,
"rentShare": 50.0
},
"EntryType": "Status Update"
}
addApplicant
Same data shape as above, plus
type (tenant / guarantor) and
reapitContactId (the applicant’s
ExternalReference). The top level also carries
Status, and the full Applicant object when the
applicant is already completed. applicationStatus on this
topic is the tenancy step integer.
applicantReopened
{
"topic": "applicantReopened",
"applicationRef": "0013524",
"applicantRef": "s0013524",
"EntryType": "Applicant Reopened"
}
Note applicantRef sits at the top level here, not inside
data. The applicant goes back to in progress;
expect further updateApplicant events.
createNewTenancy
data.address is an object
(addressLine1, addressLine2,
town, county, postcode,
country), not a single formatted string.
data.applicants lists the people on the tenancy
(reapitContactId, applicantRef,
type, applicantStatus,
reportType, rentShare). data
also carries reapitTenancyId and
reapitApplicantId (your external references),
reapitCustomerEmail, branchId,
branchName, agentId,
referencingServiceType and applicationType
(integers). Sent only for tenancies created through the Reapit
integration.
Document available
{
"topic": "uploadReapitDocument",
"applicationRef": "0013524",
"data": {
"documentType": 2,
"fileName": "Tenancy_Agreement.pdf",
"presignedUrl": "https://…s3…/Tenancy_Agreement.pdf?X-Amz-Expires=86400&…"
},
"EntryType": "Document available"
}
data comes in one of two shapes. The usual one carries a
presignedUrl: fetch the file from it within 24 hours, no
Token needed. If publishing the file failed, the same
payload carries base64 fileData instead — cap your JSON
parser, the file can be large. Handle both. For a per-applicant
document (signed receipt, KYC reports) the envelope also has
ApplicantId and ApplicantExternalReference.
documentType is an integer:
| Value | Document |
|---|---|
| 0 | Signed receipt of documents |
| 1 | Landlord confirmation document |
| 2 | Tenancy agreement |
| 3 | Tenancy agreement audit trail |
| 4 | Tenancy audit trail |
| 5 | Proof of inventory |
| 6 | Additional attachments |
| 7 | Tenant guide |
| 8 | How to Rent guide |
| 9 | How to Rent guide (easy read) |
| 10 | Tenant leaflet |
| 11 | Deposit scheme leaflet |
| 12 | Deposit scheme — custodial |
| 13 | Deposit scheme — insured |
| 14 | Inventory document |
| 15 | Counter indemnity |
| 16 | Gas safety certificate |
| 17 | Energy performance certificate |
| 18 | Electrical certificates |
| 19 | Prescribed information |
| 20 | Deposit certificate |
| 21 | Deposit terms and conditions |
| 22 | Other |
| 23 | Identity check report |
| 24 | Right to Rent report |
| 25 | Anti-money-laundering report |
| 26 | PEPs and sanctions report |
| 27 | Professional guarantor agreement |
| 28 | Rent increase notice |
| 29 | Agreed rent confirmation |
| 30 | Withdrawal notice |
KYC (no topic)
{
"Status": "COMPLETED",
"KYCid": "445566",
"UserType": 1,
"Email": "applicant@example.com",
"Reports": {
"100": "https://pre-prod.lettingshub.co.uk/webapi/v2/KYC/Report/445566/100",
"101": "https://pre-prod.lettingshub.co.uk/webapi/v2/KYC/Report/445566/101"
},
"EntryType": "Status Update KYC"
}
Status is a string: SENT,
IN PROGRESS, CONFIRMATION,
COMPLETED, CANCELLED, EXPIRED or
ABANDONED. UserType is the KYC user type
(1 tenant, 2 guarantor, 3 permitted occupier, 4 landlord, 5 buyer,
6 seller, 7 other). Reports is only present when the check
is complete; its keys are report types — 100 identity,
101 Right to Rent, 102 anti-money-laundering,
103 PEPs and sanctions — one per check that was requested,
and the same integer ends each URL. Fetch a report with your
Token header. There is no applicationRef on
this payload.
Report available
{
"ReportType": 2,
"applicationRef": "0013524",
"URL": "https://pre-prod.lettingshub.co.uk/webapi/v2/Applications/0013524/s0013524/Reports/2",
"EntryType": "Report Available"
}
Fetch the PDF with that URL (and your Token header). The
same POST also includes topic updateApplicant,
Status, applicationStatus and
data, because it is built on the applicant-status payload.
ReportType uses the report-type integers from
Dictionaries; a final report usually
arrives as a pair, the report itself and 7 (Summary). A
legacy Property Passport applicant sends Report Available
with no topic and a different URL; ignore
those.
addNote
{
"topic": "addNote",
"applicationRef": "0013524",
"Timestamp": "2026-09-15T10:12:44",
"Author": "Jane Agent",
"Content": "Called the employer, awaiting reference.",
"data": {
"typeId": "MI",
"description": "Called the employer, awaiting reference."
},
"EntryType": "UserNote"
}
EntryType is UserNote,
SystemNote or AuditTrail for applicant and
reference notes, which also carry Timestamp,
Author and Content at the top level. A
professional-guarantor note comes on the same topic with
EntryType Professional Guarantor Update and
only data. data.typeId is always
MI.
Other tenancy topics
prescribedInformationCompleted includes
isTenancyFinalised, schemeProvider and
schemeType (the deposit scheme, as text).
tenancyFinalised includes hasInventory,
daysToReportIssues (may be null), and the
prescribed-information flags.
tenancyStartDateChanged sends oldStartDate
(null when there was none) and newStartDate.
Rent increase and agreed rent
These topics follow a rent review on an existing tenancy. All of them
carry applicationRef, reapitTenancyId (the
tenancy’s ExternalReference) and ApplicationId
at the top level, plus the fields below. Money is a number, dates are
yyyy-MM-dd, frequency is one of weekly,
fortnightly, 4-weekly, monthly.
Where a document is named, fetch it through the matching
uploadReapitDocument event (types 28–30); the
documentS3Key is an internal key, not a URL.
{
"topic": "rentIncreaseNoticeIssued",
"applicationRef": "0013524",
"reapitTenancyId": "RPT-TEN-000123",
"ApplicationId": 13524,
"noticeId": 871,
"documentS3Key": "rent-increase/0013524/871/notice.pdf",
"documentName": "Rent Increase Notice.pdf",
"journalNoteText": "Rent Increase Notice issued to the tenants.",
"statusText": "Rent Increase Notice issued. New rent of £1,300 monthly due to start from 01 Jan 2027.",
"newRentAmount": 1300.0,
"newRentFrequency": "monthly",
"newRentStartDate": "2027-01-01",
"EntryType": "Rent Increase Notice Issued"
}
| Topic | Fields beyond the common three |
|---|---|
rentIncreaseNoticeIssued | noticeId, documentS3Key, documentName, journalNoteText, statusText, newRentAmount, newRentFrequency, newRentStartDate |
rentIncreaseNoticeWithdrawn | documentS3Key, documentName, journalNoteText, statusText (Complete) |
rentIncreaseWithdrawalFinalised | documentS3Key, documentName |
rentIncreaseApplied | journalNoteText, statusText (Complete), appliedRentAmount, appliedRentFrequency, appliedFromDate, previousRentAmount |
rentIncreaseTribunalReferral | dateReferred, journalNoteText |
rentIncreaseTribunalReferralWithdrawn | journalNoteText |
rentIncreaseTribunalDecisionConfirmed | confirmedRentAmount, confirmedRentFrequency, confirmedRentStartDate, journalNoteText; documentS3Key and documentName only when a decision document exists |
agreedRentConfirmationIssued | documentS3Key, documentName, journalNoteText, statusText, agreedRentAmount, agreedRentStartDate |
agreedRentConfirmationFinalised | documentS3Key, documentName |
Example receiver
curl -X POST "https://crm.example/webhooks/box" \
-H "Content-Type: application/json" \
-H "X-API-Key: <shared-secret>" \
-d '{"topic":"updateApplicationStatus","applicationRef":"0013524","applicationStatus":4}'
Dispatch on topic when present, else on
KYCid / EntryType. Return 204. Enums in
payloads are integers (documentType,
reportType, UserType), never names.
Also see:
Notifications
(POST /Notifications).
Deprecated. Use Webhooks instead.