Partner Webhook Events
This is Rhino's preferred way to receive prospect data. You emit events from your own system in your own payload format, Rhino subscribes to them, and Rhino maintains the mapping into its data model.
The appeal is that nothing on the sending side is Rhino-specific. If your platform already emits an event when a prospect is created, screened, or updated, you point that stream at Rhino instead of writing a client for it. There is no property owner slug to thread through your code and no synchronous call in your critical path.
That covers getting data in. You will still write Rhino-specific code elsewhere: an endpoint to receive Rhino's outbound webhooks, and the front end that puts the renter into the enrollment flow and reacts to its outcome.
The tradeoff is that onboarding is collaborative rather than self-serve — Rhino writes an adapter for your schema before events start landing. If you need to be integrated today without waiting on Rhino, use the Partner Prospect API instead.
How it works
- Something happens in your system — a prospect is created, screened, or updated.
- You emit an event to the destination Rhino gives you during onboarding.
- Rhino queues the event durably and processes it asynchronously.
- A normalizer written for your schema maps your fields onto Rhino's prospect model.
- Rhino resolves which property owner the prospect belongs to, then creates or updates the prospect.
- Once the prospect is eligible, Rhino emits
prospect.readyback to you with the enrollment URL.
Steps 2 and 6 are the two integration points you build. Everything between them is Rhino's.
Event envelope
Every event uses the same wrapper. Only three keys are read; anything else you include is ignored.
{
"source": "acme",
"event_type": "ProspectUpdate",
"payload": {
"acme_prospect_id": "your-prospect-123",
"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"
}
}
| Key | Description |
|---|---|
source | Your partner code, assigned by Rhino. Determines which normalizer runs. |
event_type | The event name from your system. Must be one Rhino has registered for your source. |
payload | Your event body, in your own field names |
payload is deliberately unconstrained — the field names above are illustrative, not required. Rhino's normalizer for your source is written against whatever you already emit.
An event whose source or event_type Rhino does not recognize is logged and discarded without error. That means adding a new event type on your side is a no-op until Rhino registers it, rather than a failure.
What your payload must carry
Whatever your field names, the normalizer has to be able to extract three things. An event missing any of them is dropped silently.
| Concept | Why it is required |
|---|---|
| Your prospect identifier | Becomes source_prospect_id, the key Rhino upserts on and echoes back in every webhook |
| The PMS property identifier | How Rhino determines which property owner the prospect belongs to |
| An event timestamp | Used for ordering, so late-arriving events cannot overwrite newer data |
Beyond those three, your events must also carry a terms acceptance timestamp — when the renter accepted Rhino's terms in your flow. Unlike the three above, a missing value here is not a silent drop: the event is rejected and redelivered, and will keep failing until the value is supplied. Establish where it comes from during onboarding.
Everything else is optional per-event. Fields your normalizer cannot find are left untouched rather than cleared, so partial events are safe to send.
Property resolution
Rhino looks up the property by its PMS integration code and takes the owner from there. This is why you never send a property owner slug — the property identifier you already have is enough.
The consequence is that the property must exist in Rhino, with a matching integration code, before your events arrive. Events for an unknown property are dropped silently. If prospects are not appearing, an unsynced or mismatched property code is the first thing to check with your Partner Success Manager.
Ordering and replay
Rhino stores the timestamp of the last event applied to each prospect and ignores any event at or before it. This makes the integration forgiving in the ways distributed event streams tend to need:
- Out-of-order delivery is safe. An update that overtakes an older one wins, and the straggler is discarded rather than reverting the record.
- Replays are safe. Re-emitting history will not clobber newer data.
- Duplicates are safe. A redelivered event has a timestamp equal to the one already applied, so it is treated as stale.
Emit your event timestamps from the moment the change occurred in your system, not the moment you emit. Stamping at emit time defeats the ordering guard for events that were queued or retried on your side.
Delivery and retries
Events are queued durably on receipt, so a Rhino deployment or a slow downstream does not lose them.
Processing an event either succeeds and removes it from the queue, or fails and leaves it queued for redelivery. Transient failures therefore resolve on their own without you re-emitting.
Note that the "dropped silently" cases above — unknown source, unknown event type, unresolvable property, missing identifiers — are successes from the queue's perspective. The event is consumed and not retried. These are visible to Rhino in logs but produce no signal on your side, which is the main operational difference from the API, where the same problems surface as a 422 in your own request handler.
Data that fails validation, such as a missing terms acceptance timestamp or a malformed phone number, behaves the opposite way: the event stays queued and is retried, since a redelivery is indistinguishable from a transient failure. Neither failure mode is visible to you directly, which is why the outbound prospect.created event is worth watching during onboarding — it is your confirmation that an event actually landed.
What you get back
There is no response to an event. The prospect's state reaches you through Rhino's outbound webhooks, which behave identically no matter how the prospect was ingested:
| Event | Meaning |
|---|---|
prospect.created | Rhino accepted your first event for this prospect |
prospect.incomplete | Rhino needs more data before it can check eligibility |
prospect.ready | The renter can enroll; enrollment_url is populated |
Subscribe to prospect.ready at minimum — it is how you learn the enrollment URL to send the renter to.
Because ingestion is asynchronous and one-way, this is the only way to observe the result. If you need a prospect's current state on demand, GET it from the Partner Prospect API — reading is available even if you never write through it.
Filling in missing data
A prospect stays incomplete until it has everything in Prospect Data Requirements. If your events do not carry all of it, you have three options:
- Emit more. Send a follow-up event when screening results or lease terms are finalized. Rhino applies it to the same prospect.
- Let the renter supply it. The enrollment flow collects some of it during the application. This only helps once the prospect is complete enough for Rhino to produce an enrollment URL at all.
- Use the API for the remainder. The two paths write to the same record, keyed on the same
(source, source_prospect_id). Sending the bulk over webhooks and patching specifics over the API is a legitimate combination.
Watch for prospect.incomplete to know you are in this state.
Onboarding
Rhino needs the following from you before events can be processed:
- A sample of each event you intend to send, with realistic values
- The
event_typevalues to register for your source - Which field carries your prospect ID, property ID, and event timestamp
- Your webhook endpoint for Rhino's outbound events, so you can receive
prospect.ready
Rhino then assigns your source code, writes the normalizer, registers your event types, and gives you the destination and credentials to emit to. Changes to your payload shape after go-live need a corresponding normalizer change, so tell your Partner Success Manager before you ship one.
Next steps
- Prospect Data Requirements — what Rhino needs to check eligibility
- Webhooks Overview — receiving
prospect.readyand the rest - Embedded SDA Flow — using the enrollment URL