Quick Start Guide
This walks through one end-to-end enrollment on staging: get a prospect into Rhino, wait for it to become eligible, send the renter to Rhino, and confirm the policy. Budget an afternoon.
Before you start
Your Partner Success Manager provides:
- A source code identifying you, used as
sourceon every prospect - A webhook signing secret, for verifying the events Rhino sends you
- An event destination and credentials, if you are ingesting via partner webhook events
- Auth0 credentials, if you are calling the Partner API
- Property owner slugs, unless you provision your own owner, below
If you are embedding, also send them every staging and production HTTPS origin that will frame Rhino. The same allowlist controls both iframe rendering and postMessage delivery.
Are you sending your own property owners and properties?
Skip this if Rhino already created your property owner and roster — your Partner Success Manager gave you the slug, and you can start at Step 1.
If you manage your own portfolio, provision your property owner, properties, and units first. Every step below assumes the property already exists in Rhino: Step 1's prospect can't resolve to a property that isn't there, and eligibility will never clear it. See the Partner Onboarding API Overview to create your owner and roster before starting Step 1.
Step 1: Get a prospect into Rhino
Everything starts with a prospect. Your source and your own prospect ID together identify the renter across every subsequent event and webhook, whichever path you use.
Option A: Emit an event (preferred)
Send Rhino events in your own payload shape and let Rhino map them. Before your first event, Rhino needs samples of what you emit so it can write the normalizer and register your event types — see Partner Webhook Events.
Once that is in place, emit to the destination Rhino gave you:
{
"source": "acme",
"event_type": "ProspectUpdate",
"payload": {
"acme_prospect_id": "test-001",
"external_property_id": "prop-1",
"event_date": "2026-07-24T15:04:05Z",
"first_name": "Jordan",
"last_name": "Rivera",
"email": "renter@example.com",
"phone": "5559876543",
"earliest_move_in": "2026-09-01"
}
}
The payload field names are whatever your system already uses; the example above is illustrative. What matters is that your prospect ID, the PMS property ID, and an event timestamp are in there somewhere, because those three drive upserting, property owner resolution, and ordering.
Ingestion is one-way, so there is no response to check. Move to step 2 and watch for prospect.created. If nothing arrives, the usual causes are a property whose integration code Rhino does not recognize, or a value Rhino rejects — a phone that is not exactly 10 digits is the common one.
Option B: Call the API
Push the prospect directly and get it back synchronously, including validation errors. This is the faster path to a working integration because it does not wait on a Rhino-built adapter.
$TOKEN below is an access token from POST /token, which every call in this guide needs. Every Partner API call goes to the sayrhino.com gateway shown in the environments tables; the jetty.com gateway is where you emit events, and its credentials do not work here.
curl -X POST https://api.stage.sayrhino.com/partners/acme-properties/prospects \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"prospect": {
"source": "acme",
"source_prospect_id": "test-001",
"email": "renter@example.com",
"phone": "5559876543",
"first_name": "Jordan",
"last_name": "Rivera",
"birthdate": "1990-04-12",
"lease_start_date": "2026-09-01",
"lease_end_date": "2027-08-31",
"monthly_rent_cents": 200000,
"deposit_amount_cents": 200000,
"screening_result": "approved",
"employment_status": "employed",
"yearly_income": 95000,
"has_ssn": false,
"terms_accepted_at": "2026-07-24T15:04:05Z"
}
}'
A success returns the prospect. The HTTP status is 200 whether the prospect was created or updated — the distinction arrives in the body's status_code field, which reads 201 on a create. What you act on is the status field, which on this first call will be processing — Rhino has everything it needs and is checking eligibility.
phone is the format most likely to bite you here: it must be exactly 10 digits, with no country code and no punctuation. +15559876543 and (555) 987-6543 are both rejected with a 422 and "Phone is not valid".
If you get incomplete instead, a required field is missing. Compare against Prospect Data Requirements and re-POST with the missing values; the endpoint is an upsert, so repeat calls update the same record.
Which fields count toward completeness depends on your enrollment source. Sources configured for optional ingest fields do not need screening_result, employment_status, or yearly_income — omitting one of those does not hold the prospect at incomplete. Everything else in the payload above is required on every source. Confirm with your Partner Success Manager which applies to you.
Both options write the same record keyed on (source, source_prospect_id), so you can start on the API and move to events later without changing anything downstream.
Step 2: Wait for eligibility
Eligibility runs asynchronously. When it finishes the prospect becomes ready. That means processing is done, not that the renter can enroll — Rhino marks an ineligible prospect ready too.
Register a webhook endpoint so you find out. If you ingested via events, this is not optional — it is the only way the enrollment URL reaches you:
curl -X POST https://api.stage.sayrhino.com/partners/acme-properties/webhooks/endpoints \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"endpoint": {
"name": "Staging",
"destination_url": "https://your-server.com/webhooks/rhino",
"events": ["prospect.ready", "policy_application.declined", "policy.created"]
}
}'
While you are building, it is fine to fetch the prospect directly. Reading works even on a webhook-first integration, so this is the quickest way to see whether your events landed:
curl https://api.stage.sayrhino.com/partners/acme-properties/prospects/acme/test-001 \
-H "Authorization: Bearer $TOKEN"
Gate on enrollment_url, not on status. The URL is present only once the prospect is eligible for at least one product, which needs Rhino to have resolved its property and unit; otherwise it comes back as null alongside status: ready. A null there is your signal to read ineligibility reasons on GET. sdi_ineligibility_reasons and cd_ineligibility_reasons carry human-readable strings and are omitted from the GET response when there is nothing to report. "Property must be present" — Rhino could not resolve the property — is the usual entry. When guarantor_coverage is true, GET also includes rg_ineligibility_reasons (an array; [] means no reason has been recorded) and rg_eligibility_status (eligible or ineligible). rg_eligibility_status is omitted while eligibility is pending, so an empty reasons array during processing is not a decline.
The prospect.ready webhook does not include the SDA or cash-deposit reasons. Use GET for those. When guarantor_coverage is true, the webhook does include the Renter Guarantee fields: rg_eligibility_status (omitted until eligibility has been decided) and rg_ineligibility_reasons.
offered_products will still be empty at this point. Rhino cannot price the products until the renter supplies the last few underwriting inputs inside the enrollment flow, so plan your UI around sending the renter to Rhino rather than quoting them beforehand.
Step 3: Receive webhooks
Your endpoint must verify the signature before trusting anything in the body. The signature covers the raw request body, so read it before any JSON middleware touches it.
const crypto = require('crypto');
app.use('/webhooks/rhino', express.raw({ type: 'application/json' }));
app.post('/webhooks/rhino', (req, res) => {
const rawBody = req.body.toString('utf8');
const header = req.headers['x-webhook-signature'];
const timestamp = /t=(\d+)/.exec(header)?.[1];
const v1 = /v1=([0-9a-f]+)/.exec(header)?.[1];
const expected = crypto
.createHmac('sha256', process.env.RHINO_WEBHOOK_SECRET)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
// timingSafeEqual throws rather than returning false on a length mismatch
const signatureValid =
v1 &&
v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
if (!signatureValid) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(rawBody);
switch (event.event) {
case 'prospect.ready':
onProspectReady(event.data.third_party_prospect);
break;
case 'policy_application.declined':
onDeclined(event.data.policy_application);
break;
case 'policy.created':
onPolicyCreated(event.data.insurance_policy);
break;
}
res.status(200).send('OK');
});
Respond within 10 seconds. Queue anything slower. Full details in the Webhooks Overview.
Step 4: Send the renter to Rhino
Use the enrollment_url from the prospect response verbatim. It already contains the flow type, property owner, source, prospect ID, and the required email parameter.
For an embedded integration, load it in an iframe:
<iframe
id="rhino-iframe"
src="<enrollment_url from the API>"
width="100%"
height="800"
frameborder="0"
allow="payment">
</iframe>
For an expedited integration, redirect to it. You can append redirect_url to control where the renter lands afterwards, but Rhino must have allowlisted that domain for your source or the parameter is ignored.
Once the renter has been underwritten, offered_products gains links with the product and cadence preselected, which are useful for sending them back to a specific choice.
Step 5: Handle the outcome
Embedded integrations receive a postMessage when the flow reaches a terminal state. The payload is a plain string — completed, declined, or blocked — not an object.
window.addEventListener('message', (event) => {
if (event.origin !== 'https://www.stage.sayrhino.com') return;
if (event.data === 'completed') {
showSuccessState();
} else if (event.data === 'declined' || event.data === 'blocked') {
showFallbackDepositOptions();
}
});
Use this to update your UI, and the policy.created webhook to update your records. The browser message is a hint; the webhook is the fact.
Step 6: Test the edge cases
- Success — prospect goes
ready, renter enrolls, you receivepolicy.created - Incomplete prospect — omit
birthdate, which is required on every source, and confirm you handleincomplete - Declined — confirm your UI degrades gracefully on
declined - Abandonment — close the tab mid-flow, reopen the same enrollment URL, confirm the renter resumes
- Duplicate webhooks — replay a delivery and confirm you deduplicate on
X-Webhook-Delivery - Mobile — check the iframe height on a narrow viewport
If you ingest via events, also exercise:
- Out-of-order events — emit an update with an older timestamp than one already applied and confirm it does not revert the prospect
- Replay — re-emit an event you already sent and confirm nothing changes
- Unknown property — emit an event with a property ID Rhino does not have, and confirm you notice the resulting silence
Staging lets you reset test data so you can reuse identifiers:
curl -X DELETE "https://api.stage.sayrhino.com/residents/reset?email=renter@example.com&owner_slug=acme-properties" \
-H "Authorization: Bearer $TOKEN"
Step 7: Go live
- Request production credentials from your Partner Success Manager
- Point events at the production destination, and any Partner API calls at
https://api.prod.sayrhino.com - Create a production webhook endpoint — endpoints do not carry over from staging
- Confirm your production domain is allowlisted for framing and events
- Put monitoring and alerting on your webhook endpoint before the first renter hits it, not after
- Soft launch on a limited set of properties for a couple of weeks before opening it up
- Watch your first live enrollments through the deliveries endpoint
If your organization requires a security review of the integration, start it early. Reviews here typically run a test suite against the finished, production-ready integration rather than against a design document, so it gates go-live rather than running alongside the build.
Next steps
- Partner Onboarding API Overview — provisioning your own property owners, properties, and units
- Partner Webhook Events — the preferred ingestion path in full
- Partner Prospect API — full endpoint reference
- Authentication & Environments — credentials, base URLs, and slugs
- Embedded purchase flow — iframe details
- Event Reference — every webhook payload