Skip to main content

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 source on 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 and property owner slugs, if you are calling the Partner API

If you are embedding, also send them the domain you will frame Rhino from and the origin that should receive postMessage events. Nothing renders in an iframe until those are allowlisted.

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 cause is a property whose integration code Rhino does not recognize.

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.

curl -X POST https://api.ext.stage.jetty.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 201 means the prospect was created. The response includes a status, which on this first call will be processing — Rhino has everything it needs and is checking eligibility.

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.

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 and gains an enrollment_url.

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.ext.stage.jetty.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.ext.stage.jetty.com/partners/acme-properties/prospects/acme/test-001 \
-H "Authorization: Bearer $TOKEN"

Once status is ready, enrollment_url is the link to send the renter to.

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 stringcompleted, 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 receive policy.created
  • Incomplete prospect — omit yearly_income and confirm you handle incomplete
  • 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.ext.stage.jetty.com/residents/reset?email=renter@example.com&owner_slug=acme-properties" \
-H "Authorization: Bearer $TOKEN"

Step 7: Go live

  1. Request production credentials from your Partner Success Manager
  2. Point events at the production destination, and any API calls at https://api.ext.jetty.com
  3. Create a production webhook endpoint — endpoints do not carry over from staging
  4. Confirm your production domain is allowlisted for framing and events
  5. Put monitoring and alerting on your webhook endpoint before the first renter hits it, not after
  6. Soft launch on a limited set of properties for a couple of weeks before opening it up
  7. 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