RentalTideRentalTideRentalTideDocs
Dashboard›

Embedded insurance

Offer your coverage inside a RentalTide checkout — quote before the renter pays, ask your underwriting questions in our UI, hold the premium on their card, bind the policy, then charge it on your behalf.

A renter books a boat on RentalTide. Before they pay, they are offered your coverage, priced by you, presented in our interface. If they take it, they answer your underwriting questions inside our checkout, check out once, and you bind the policy. The premium is its own charge, never part of the operator's: either made on your behalf and taken only once your policy exists, or, if you hold premium yourself, charged on your own Stripe account and paid before we ask you for the policy (see Money). Afterwards they see the policy and its documents on their booking page, and anyone who declined gets a second chance to buy.

You are not building a web app. You expose a handful of HTTPS endpoints. We do the rest: the coverage card, the question rendering, mobile and dark mode, localisation, the payment, the tax, the receipt, the order summary, refunds, the follow-up offer, document display, and the operator-facing reporting.

Build the whole thing before anyone reviews you

Your offering works on sandbox locations from the moment you create it, with no approval step — quoting, questions, requoting, binding, the receipt, the Next Steps page. Approval is only needed to sell to real renters.

The renter's journey

StepWhereWhat happens
1Booking flowRenter picks a boat and a time. We call your /quote. Your coverage appears as a card with a real price.
2Booking flowThey select it. It joins the order as an estimate.
3CheckoutWe render your questions in our UI. They answer.
4CheckoutWe call /requote with the answers and get a final price.
5CheckoutThey check out once. The rental is charged; your premium is its own charge on the same card.
6After paymentWe call /bind. You return a policy. The premium is captured after it, or before it if you collect it.
7Next Steps pageWe show the policy and document, and offer the coverage again to anyone who declined.

The renter never leaves RentalTide and never sees a third-party form.

Getting set up

1. Create a partner account

partners.rentaltide.com. Free, active immediately, no approval queue for the account itself.

2. Connect a payout account

Payouts → Connect with Stripe. Standard Stripe Express onboarding: bank details, business verification. Do this early — your coverage offering cannot be approved for production without it, and the account you connect here is attached to your offering automatically.

We can pay into the US, UK, EEA, Canada and Switzerland. The country list only shows those, because being told after you have earned money that we cannot send it to you is not a conversation anyone should have.

Or collect the premium yourself. If you must hold premium from the first cent (premium trust, producer licensing), skip the payout account and use Embedded insurance → How you collect the premium → Connect your Stripe account instead. That links the Stripe account you already run, through Stripe's own sign-in. Every premium is then a direct charge on that account, refunded by you. See Money.

3. Scaffold your endpoints

Bash

The scaffold is built on @rentaltide/underwriter, so what you edit is underwriting and nothing else:

JavaScript

The SDK handles the parts that are easy to get quietly wrong:

  • The signature — timing-safe, timestamp-bounded, and taken over the raw bytes. JSON.stringify of a parsed body does not round-trip byte for byte.
  • Idempotent binds — it remembers the policy against its quoteId, so our retry cannot make you write a second one. Memory by default; pass a store before you sell a real policy.
  • The rules below, checked where your handler returns rather than becoming a card that silently never renders.

It runs anywhere: rentaltide.handler() for Node, rentaltide.fetch for Workers, Hono or a Next route handler.

Not using Node? Everything in the SDK is the contract below, implemented once. Implement it directly and coverage test judges it exactly the same.

4. Register the offering

Bash

It reads rentaltide.coverage.json, registers the offering, writes the id back, and saves your signing secret for the CLI. Put that secret in your own environment too — export SIGNING_SECRET=whsec_… — and restart your server.

Your signing secret is shown exactly once

No endpoint returns it again. coverage create saves a copy to ~/.rentaltide/credentials.json before printing it, so a closed terminal does not lose it — but only on that machine.

The same call over HTTP, if you would rather not use the CLI:

HTTP
POST https://v3.api.rentaltide.com/developer/coverage-providers
Authorization: Bearer <your partner token>
Content-Type: application/json

{
  "name": "Example Marine Cover",
  "baseUrl": "https://api.example-underwriter.com/rentaltide",
  "supportEmail": "help@example-underwriter.com",
  "supportedCountries": ["US"],
  "supportedCurrencies": ["USD"],
  "quoteTimeoutMs": 800
}

Then rentaltide coverage link and rentaltide coverage secret to point the project at it.

  • baseUrl must be https. We sign every call to it and send renter identity at bind; over plaintext a signature proves nothing to anyone watching. It can point anywhere while you build — see step 6.
  • supportedCountries is where you are licensed to write. Empty means nowhere, not everywhere.
  • quoteTimeoutMs is capped at 5000 and runs while a renter watches a page repaint.

5. Prove the contract

Bash

Signs with your real secret — same headers, same {timestamp}.{body} payload, same deadlines — so your signature verification is proven before a booking exists. It checks the rules that fail quietly:

✓ Refuses an unsigned request
✓ Quote answers inside 800ms                 2ms
✓ Premium is a JSON number
✓ Deposit effect is coherent
✓ A decline is a 200 with decision: declined
✓ Bind is idempotent on quoteId
✓ Policy document is https

Each of those is something that type-checks, answers 200, and breaks a renter. It exits non-zero, so keep it in your CI.

The test /bind carries a payment block shaped like a sandbox purchase made before you link a Stripe account: stripeAccountId: null, livemode: false. That shape means there is no charge to look up, so skip your payment check for it. If we collect your premium instead, pass --no-payment (CLI 0.6.1 or later).

Individual calls are commands too, for while you are still writing it:

Bash

6. Test in sandbox

Deploy your endpoints somewhere we can reach, point the offering at it, and run the whole renter flow against a real test marina:

Bash

That repoints the offering's base URL while you work and restores it when you stop. Then open the sandbox booking page, pick a boat, and watch your card appear, your questions render in the checkout, and your policy bind after a test card is charged.

Nothing is transferred in sandbox.

7. Submit for review

Bash

Or POST https://v3.api.rentaltide.com/developer/coverage-providers/{id}/submit.

We check you are actually ready before approving — a payout account, at least one licensed country, a signing secret — and tell you exactly what is missing if not.

8. Approved

A merchant enables your offering for their locations — one switch on their Coverages page — and it appears in their renters' checkout. Registering with us is not permission to appear in somebody's checkout; only the merchant grants that, and they can withdraw it at any time.

You have a switch of your own. Accepting quotes is on from the moment you create the offering; turning it off stops every call we would make to you, in sandbox and production, without unwinding anything you have already bound. It is the control to use when your service is down.

Changing things after approval

Changing where data or money goes — your baseUrl, payout account, licensed countries or currencies — sends the offering back for review, and it keeps working in sandbox meanwhile. Name, support address, quote timeout and the on/off switch change freely.

Calling your own endpoints

Every call below can be fired at your server by the CLI — rentaltide coverage quote | schema | requote | bind | decline | reschedule | cancel — signed exactly the way we sign it. Each prints the signed request, the status, the timing and the body, and exits non-zero on anything but a 200. --data '{…}' replaces the body outright; --url overrides where it goes.

The contract

We always call you. The one exception is the messaging endpoint at the end.

Each call is shown twice: what you write with @rentaltide/underwriter, and the wire format underneath for anyone implementing it directly. The two are the same contract — the SDK is it, written once.

Every request we send carries:

X-RentalTide-Signature: v1=<hex HMAC-SHA256 of "{timestamp}.{raw body}">
X-RentalTide-Timestamp: <ISO 8601>

Verify both. Reject stale timestamps.

POST /quote

As soon as the renter has chosen a boat and a time. No renter identity is sent — at this point they have bought nothing and agreed to nothing, and most visitors never will.

JavaScript

On the wire:

JSON

Everything above the flat fields is resolved by us, not taken from the browser: the asset facts are re-read from the listing by id, and place.country is the same resolution the licensing gate used. A renter cannot shop for a cheaper band by editing a request.

The flat fields at the bottom are the original contract and are still sent, so a provider written against the first version of this page keeps working untouched.

`place.region` can be null, and you should decline when it is

country is resolved for every location. region is not: it comes from a value a merchant set deliberately or from the state on their Stripe account, and it is never parsed out of the address line — a street address is free text, and a tax filing is the last place to start guessing.

So a location whose state we do not hold sends region: null. If you rate or remit on the subdivision (surplus lines tax is remitted to the state a policy is written in), decline that quote rather than assume one. We would rather lose the sale than have you file in the wrong state.

Tell us which of your locations come through without a region and we will chase the merchants for them.

Signals are circumstance, never identity

signals carries how far ahead they booked, what hour they are starting, how long they have been deciding, roughly where the request came from. It carries no IP address, no user agent and no email — at quote time the renter has agreed to nothing, and most visitors never buy. Identity arrives once, at /bind, where there is a contract to justify it.

Response:

JSON

The last four are optional, and each is ours to draw, never yours: text is plain, every link must be https, and nothing is rendered as HTML.

  • premiumTax is the insurance premium tax inside premium. It is shown on the order summary, and your share of the premium is taken on the premium before it (see Money).
  • brand puts "Powered by" your logo and a "Learn more" link on the coverage card.
  • attestation is a checkbox the renter must tick to buy, in your exact words. Each link's label must appear verbatim in text; we turn those words into the link. Checkout refuses a purchase without the tick, and we keep the words and the time it was ticked on the sale.
  • declineAcknowledgement is for places where a renter who declines must sign that they understood what they refused (Florida s. 327.54 requires exactly this, at the price declined). Put the price into text yourself. A renter who does not take your coverage is shown it in a blocking dialog with questions, and must confirm before paying. We send you what they signed on /decline once their booking is paid.

depositEffect is one of none, waive, reduce_to, reduce_by, reduce_by_percent. Anything but none or waive must carry a depositEffectValue: a reducing effect with no number renders to the renter as a saving and then changes nothing at hold time, so we reject it.

Budget: 800ms

Miss it and your card does not render; the booking proceeds without it. No renter waits on an insurance quote to see a checkout button.

A premium must be a JSON number. "44.50" is rejected.

POST /schema

We ask once per questionSchemaVersion and cache it, so checkout never waits on you to draw a form.

JavaScript

On the wire:

JSON

Supported types: text, number, date, checkbox, dropdown, multiselect, address, and notice for a disclosure block.

  • Schema, not markup. No HTML, no scripts. A field type we cannot draw is dropped rather than rendered half-finished.
  • Pinned per quote. Changing your schema never alters a quote in flight.
  • Patterns are bounded. A regex with a nested quantifier ((a+)+) is refused: it runs against renter input on our servers.
  • At most 12 fields. Each one is friction at the moment somebody is deciding to pay, and we measure checkout completion with and without your coverage selected.

POST /requote

When the renter submits your questions.

JavaScript

On the wire:

JSON
JSON

premium is the most the renter can be charged: it is the amount held on their card at checkout. If your bound price comes in lower, return it from /bind and only that is taken.

decision is accepted or declined. A decline is a normal outcome: we remove the coverage and the renter completes their booking without it.

Answers are validated against your pinned schema on our side first, so minAgeYears and pattern are enforced before they reach you. Conditional fields the renter never saw are not sent.

POST /bind

Only after the booking is paid and your premium is held on the renter's card. premiumCharged is that hold. Nothing is captured until you answer: a policy you return is paid for, a refusal releases the hold and the renter is never charged. This and /decline are the only calls carrying renter identity.

If you collect the premium yourself, the order is the other way round: the premium has already succeeded on your Stripe account when /bind arrives, and the request carries a payment block naming the charge. Retrieve it on your account and refuse to issue if it does not match. premiumCharged equals payment.amount.

JavaScript

The SDK remembers the policy against its quoteId, so our retry returns the same one rather than calling your handler twice. In production give createUnderwriter a store, or a restart forgets and the next retry issues a second policy.

On the wire:

JSON

With the premium on your own account, the same request also carries:

JSON

stripeAccountId is your account. It is null only in sandbox, when you have not connected an account in test mode yet and the test charge was made on ours. livemode is false for anything sold on a sandbox location.

operators is everyone who signed a waiver for this booking, not just whoever paid, because a policy is written against the people who will be on the water. Names and waiver status only: you already have the buyer's contact details, and the other operators never agreed to theirs being passed on. It is null when the merchant only collected one name.

location is where the business is; launchLocation is where the boat goes in, when the merchant runs delivery or a separate ramp. They are regularly not the same place, and location.region is the same resolution your quote was rated against.

nameOnCard is here because it is the field that disagrees with the booking name in a fraud case. The card number never is, and never will be — brand and last four are the most a policy file needs and the most PCI lets us pass on.

Waiver status, not waiver answers

You get whether a waiver was required, whether it was signed, when, and which template. You do not get the answers, which routinely carry health information — a different category of data with a different legal basis. If your underwriting needs a medical disclosure, ask for it in your own /schema, where the renter can see what they are answering and why.

JSON

premium and tax are optional. Return premium when the policy was bound at a lower price than premiumCharged: we capture that instead, and the rest of the hold is released (if you collected it, we refund the difference from the charge on your account). A policy bound above what the renter paid cannot be paid for; we cancel it with you (/cancel) and the renter gets their money back. Decline rather than do that.

A 4xx is a refusal: the hold is released at once, or, if you collected the premium, we refund the charge on your account (no policy exists, so there is nothing for you to cancel). With the SDK, call refuse(reason) (0.1.2 or later); anything else you throw is a 500.

A 5xx or a timeout (15 seconds) is retried with the hold or the charge kept: first at checkout, then every 5 minutes, 5 attempts in all (about 20 minutes, or sooner if the trip ends). Every retry carries the same quoteId and the same charge, so a policy you wrote whose response was lost comes back on the next attempt. After the last one we release or refund, with refund metadata reason: policy_not_bound. No /cancel follows, because we never received a policyId; listen for charge.refunded on your account.

A quote backs exactly one purchase. Once a quote has been bought, it is never bought again, even if that purchase is later cancelled or refunded: a renter who buys again gets a fresh quote and a new quoteId. So the only repeat /bind you receive for a quoteId is a retry of the same purchase, with the same charge.

Idempotent on quoteId

We retry on timeout or 5xx. Returning the same policy for the same quote must not write a second one. documentUrl must be https.

POST /decline

Only if your quote carried a declineAcknowledgement, and the renter declined your coverage and signed it. Sent once their booking is paid, with their identity, because the record is theirs and you are the one required to hold it.

JSON
JSON

text is the wording you sent with that quote, price included, never a copy the browser held. Keyed on the quote, so a retry is the same acknowledgement. We retry failures for a while; a booking that is cancelled or never paid is never sent.

POST /reschedule

When a booking's dates move after the policy is bound. Rentals get rescheduled, extended at the dock, and corrected on the order page, so the window you priced is not always the window the renter turns up for.

JavaScript

On the wire:

JSON
JSON

decision is accepted or declined, with an optional reason. A bare 200 with no decision is read as acceptance.

This is not a requote. The renter has already paid, and there is no path to charge them more without asking them again, so a policy that would cost more for the new dates is a decline. One that costs less can say so in premium, and we refund the renter the difference (your share comes back in proportion). If you collect the premium yourself, the difference is yours to refund, from the charge on your account; we do not refund it as well. A decline is raised to the operator as a customer who paid and is no longer covered.

We detect the change by comparing the window you were bound against with the booking's current dates, so every way a date can move is covered — not only the renter-facing reschedule. Keyed on the policy plus the new window, so a retry after a timeout is the same amendment rather than a second one.

POST /cancel

JavaScript

On the wire:

JSON

If we collected the premium, we refund the renter once you answer 2xx. If you collected it, a 2xx means you have refunded it, or will, from the charge on your account: we do not refund it as well, because that would pay the renter twice. We read the charge back afterwards to show the operator what came back.

A 5xx or a timeout (10 seconds) is retried every 15 minutes, 6 attempts in all (about 75 minutes). After the last one, the sale is flagged to our team, who will contact you to cancel and refund it by hand. A 4xx on /cancel is treated the same as a failure, so do not use one to mean "already cancelled": answer 2xx for a policy that is already void.

Matching your products to their boats

productCode on a quote is which of your products covers this listing. Left alone it is whatever the offering was created with, which means a jet ski and a 24' pontoon arrive at you indistinguishable.

The screen where an operator picks belongs in your app, not ours: it is your product list, in your words, and you are the one who knows which product fits a boat. Build it as an app with a settings page, then write the operator's choices back:

HTTP
PUT https://v3.api.rentaltide.com/marketplace/apps/{clientId}/coverage-mappings
Authorization: Bearer <the installed app's token>

{
  "mappings": [
    { "inventoryId": "34b600fb-…", "productCode": "pwc-standard" },
    { "inventoryId": "9c21af03-…", "productCode": "pontoon-liability",
      "metadata": { "ratingTier": "B", "hullValueBand": "40-60k" } }
  ]
}

GET the same path to read them back. A productCode of null or "" clears the mapping and that listing falls back to the offering's own code. metadata is yours — we store it and hand it back, and never look inside it.

From then on every /quote for that listing carries the code the operator picked, already resolved.

What the write is scoped to

The provider is derived from the app's owner, never from the request: an app can only write mappings for coverage offerings belonging to the same partner, only for the tenant it is installed on, and only for listings that belong to that tenant. An unknown listing comes back in unknownInventoryIds rather than being written.

It also requires the merchant to have your offering enabled. Registering with us is not permission to write against somebody's account.

Without an app, you can still map on your side: every quote carries asset.inventoryId, which is stable per listing, along with the category, capacity and seating capacity.

Estimate, then final price

Between the card and the questions the renter is looking at an estimate.

OutcomeWhat we do
Same as the estimateProceed silently
LowerProceed, show the new price
HigherStop. The renter must explicitly accept the new price before paying
DeclinedRemove the coverage, let the booking continue

We never charge a renter more than the price they were shown without asking, and we compare against our own record of what was displayed, not a number sent back by the browser.

If final prices routinely differ from estimates, the estimate is doing harm. Worth agreeing a tolerance up front and measuring it.

Money

The premium is never part of the operator's charge, fees or ledger. You choose who collects it.

You collect it, on your own Stripe account

For an underwriter that has to hold premium from the first cent. Connect the Stripe account you already run (step 2 above).

  • Two charges, one checkout. The rental is charged to the operator. Your premium is a direct charge on your account, on the same card, with your name on the renter's statement. It never passes through our balance or the operator's.
  • Paid before the policy. The charge has succeeded before we call /bind, and /bind names it in payment. A card that will not pay means no /bind at all.
  • No fee from us is taken out of the premium.
  • Refunds are yours. /cancel on a bound policy is your instruction to refund it; a cheaper /reschedule is yours to refund the difference on. We refund only a premium no policy was ever bound against (a refused or failed /bind), from the charge on your account.
  • Disputes on the premium are yours, as on any charge on your account.

We collect it, on your behalf

  • The premium is its own charge, made on your behalf. The renter checks out once. The rental is charged to the operator; your premium is a separate charge on the same card with your connected account as the settlement merchant (on_behalf_of), so your name is on the renter's statement and the money is never the operator's.
  • Held, then taken. The premium is authorised when the booking is paid, /bind is called, and only a policy you return is captured, at the price you bound it at and never above the hold. A refusal releases the hold.
  • Our share is your agreed commission on the premium before premium tax, taken as the charge's application fee. Stripe moves the rest to you at capture. There is no separate payout to wait for or reconcile.
  • Refunds. A cancelled booking cancels the policy (/cancel) and refunds the renter's premium; your share comes back with it, and so does ours. A reschedule that is cheaper refunds the difference. Nothing is refunded without one of those.
  • Sandbox moves no money to you. Everything runs; the premium is a test charge on our account and nothing settles to yours. If you collect the premium yourself and your account is connected, sandbox charges it on your account in Stripe test mode, so you can check the payment block end to end.

Talking to the renter

You have real reasons to write to the customer: here is your policy, here is your document, your claim was settled. We will not hand over an address, so instead you ask us to send it:

HTTP
POST https://v3.api.rentaltide.com/coverage-provider/messages
X-RentalTide-Provider: your-slug
X-RentalTide-Signature: v1=...
X-RentalTide-Timestamp: ...

{
  "policyId": "POL-88213",
  "category": "policy_document",
  "subject": "Your coverage documents",
  "body": "Plain text. Paragraphs separated by blank lines.",
  "documentUrl": "https://.../policy/POL-88213.pdf",
  "reference": "your-own-id-for-retries"
}

Categories: policy_issued, policy_document, policy_cancelled, claim_update. There is no custom.

  • We resolve the recipient from the policy. You never name an address.
  • Plain text only. It is escaped and wrapped in our template, attributed to you, sent from our authenticated domain with your support address as reply-to.
  • One of each category per policy. A retried webhook is a no-op, not a second email.
  • The merchant must have your offering enabled. If they turn it off, messages stop immediately.

When something fails

Coverage is an add-on. Nothing about it may block a rental.

FailureWhat happens
/quote times out or errorsYour card does not render. Booking proceeds.
/requote failsCoverage is dropped from the order. Booking proceeds.
/bind fails after paymentThe booking stands. It is flagged to the operator within 30 minutes, and the renter's page says the policy has not issued yet.
Your service is downThe same, at every step.

If you have a status endpoint or a maintenance signal, tell us — we would rather stop offering your product cleanly than time out on every page load. Switching Accepting quotes off does the same thing immediately.

When we do not call you at all

Worth knowing before you go looking for a bug on your side. We resolve each location's country from what the merchant has stored and fail closed: no country, no quote request, because selling a policy outside where you are licensed is worse than selling none. The same silence applies when the settlement currency is not one you price in, when the merchant has not enabled you, when your offering is switched off, and — for a production location — until you are approved.

What we need, and what you need

From you

  1. Sandbox endpoints for /quote, /schema, /requote, /bind, /reschedule and /cancel
  2. One product definition: name, description, terms, coverage limit, and what it does to the security deposit
  3. Your question schema
  4. The countries and currencies you are licensed to write in
  5. Stripe Connect onboarding, from the Payouts page

From us

  1. A partner account and your signing secret
  2. A sandbox marina with test inventory, so you can run the full renter flow
  3. This document as the contract between us
  4. A data-sharing agreement naming the controller for the renter data sent at bind, before you go live

Things we answer per integration

A merchant can run their own damage coverage and yours at the same time. Both appear in the same list, and the merchant decides which products are active per location. You are not competing for a single slot.

Date changes reach you within the hour. A booking that moves after bind calls your /reschedule. If you would rather receive it immediately or not at all, say so during review.

Cancellation. A voided or refunded coverage sale calls your /cancel and reverses the transfer. The reversal is attempted even when your /cancel fails: the money is ours to recover either way, and a disagreement about a cancellation is a conversation, not a reason to leave the funds out.

Claims handling and premium tax are yours to state. Who takes a claim, and whether a region charges insurance premium tax rather than sales tax, differ by underwriter and by country. Tell us in your submission and we will render it at the point of sale rather than guess a default.

Anything else: partners@rentaltide.com.

Was this page helpful?
Need help? Contact Support.See what’s new. Check out changelog.Questions? Book a video chat.
Ask AI
Responses are generated using AI and may contain mistakes.
Ask questions about RentalTide and get help with your integration.