Curb Hero

Embed SDK

A full Curb Hero panel, inside your property portal.

Drop two lines into any page — Webflow, Wix, Yardi, Buildium, RealPage, AppFolio, plain HTML. Residents sign in through a popup, then see their houses, today's pickup status, and the driver's proof photos right inside your portal.

01

Quick start

Paste this anywhere on the page where you want the resident panel to appear. The <script> can live in the <head> and the <div> in the body — order doesn’t matter, the SDK waits for DOMContentLoaded.

<script
  src="https://curbherotrash.com/embed/curbhero.js"
  data-partner-id="YOUR_PARTNER_ID"
  async
></script>
<div id="curbhero-signup"></div>

02

The auth handshake

Residents authenticate without ever leaving your page. The SDK opens a popup at curbherotrash.com/embed-loginthat runs Firebase Auth on the Curb Hero origin. On successful sign-in, the popup sends the resident’s Firebase ID token back via postMessage and closes itself.

  1. 1Resident clicks the Sign in button in the embed. SDK calls window.open() with width=480, height=660, centered.
  2. 2Popup loads /embed-login and renders the email/password form. The partner ID is preserved as a query parameter for attribution.
  3. 3Resident signs in. Firebase mints an ID token, the popup posts { source:'curbhero-embed', type:'auth', idToken, profile } to window.opener, and closes itself.
  4. 4SDK receives the postMessage, validates event.origin === 'https://curbherotrash.com', stores { idToken, profile } in sessionStorage scoped to the partner ID, and renders the signed-in panel.
  5. 5SDK fetches /getEmbedStatus with Authorization: Bearer <idToken> to load the resident's houses + today's pickup status, then polls every 30s while the tab is visible.

If the popup is blocked, the SDK falls back to a same-tab navigation so the user can still sign in. The opener-page listener stays armed across that journey if the resident returns.

Popup → opener message

// Posted by the popup at /embed-login → window.opener
{
  source: "curbhero-embed",
  type:   "auth",
  idToken: "<Firebase ID token, ~1h validity>",
  profile: { uid, fullName, email },
  partnerId: "grovewood"
}
Note:The popup posts with targetOrigin: "*"because it cannot know its opener’s origin. The SDK strictly checks event.origin === "https://curbherotrash.com" when receiving the message — never trust a postMessage source by content alone.

03

What residents see

The panel has two states. Both render inside the same bordered card, so the partner page’s layout never shifts when the resident signs in or out.

Signed out

  • Title + one-line value prop
  • Primary CTA: Sign in (opens popup)
  • Secondary CTA: Get started · new account (opens /signup in a new tab with the partner ID attached)
  • Microcopy: "Opens in a popup · billed monthly · cancel anytime"

Signed in

  • Greeting (Hi <first name>)
  • Sign out button in the corner
  • One card per household: address, subscription pill (Active / Pending billing), today's pickup status pill (Cans out / Cans back / Done), the assigned driver's first name
  • Driver photo thumbnails (take-out + bring-back) when uploaded — click to open full size
  • Footer actions: + Add home, Start billing or Manage subscription (opens /app in a new tab)

The panel auto-refreshes every 30 seconds while the tab is visible, so a driver uploading a take-out photo at 9:14 PM shows up in the resident’s embed by 9:15 with no page reload. Refresh pauses while the tab is hidden to avoid background work on closed but still-open partner tabs.

04

Configuration

Configure the embed purely through data-* attributes on the <script> tag — no inline JS needed.

AttributeRequiredDefaultNotes
data-partner-idrequiredYour partner ID — generate one instantly in the “Get a partner ID” section below. Required for attribution and (optionally) for partner OAuth at signup.
data-targetoptional#curbhero-signupCSS selector for the mount point. The SDK renders inside the first matching element.
data-labeloptionalSign inText on the signed-out primary CTA. Tune to match your portal's voice ("Sign in to view trash service", "Open Curb Hero", etc.).
data-baseoptionalhttps://curbherotrash.comOverride the base URL for login + signup links. Useful only when pointing the embed at staging.
data-functions-baseoptionalhttps://us-central1-curbhero-bd084.cloudfunctions.netOverride the backend URL the embed calls for resident data. Same staging-only caveat.

Recipes

Custom CTA label

Tune the sign-in button copy to match your portal's voice.

<script
  src="https://curbherotrash.com/embed/curbhero.js"
  data-partner-id="grovewood"
  data-label="Sign in to view trash service"
  async
></script>
<div id="curbhero-signup"></div>

Mount inside a custom layout

If your CMS or portal already has a slot for partner widgets, point data-target at the right selector.

<script
  src="https://curbherotrash.com/embed/curbhero.js"
  data-partner-id="grovewood"
  data-target=".services-grid .curbhero-slot"
  async
></script>
<div class="services-grid">
  <div class="curbhero-slot"></div>
</div>

05

postMessage events

Anything the host page wants to react to (analytics, layout tweaks, telemetry) it can read off of window.postMessage. The embed emits four event types.

// Anything the host page can listen for from the embed
window.addEventListener("message", (event) => {
  const msg = event.data;
  if (msg?.source !== "curbhero-embed") return;
  switch (msg.type) {
    case "render":       /* SDK mounted into the DOM */ break;
    case "auth-success": /* resident signed in; msg.profile available */ break;
    case "auth-fail":    /* token rejected or expired; msg.reason set */ break;
    case "sign-out":     /* resident manually signed out */ break;
  }
});

render

Fires once when the SDK has mounted into the DOM. Use for analytics or to remove a loading placeholder.

auth-success

Fires after the popup handshake completes. The payload includes the resident's profile ({ uid, fullName, email }).

auth-fail

Fires if the stored ID token is rejected by the backend (typically expiry). The payload includes a reason string.

sign-out

Fires when the resident clicks Sign out from inside the embed.

06

API contract

If you want to consume Curb Hero data outside the embed (for example, surfacing pickup status inside your own dashboard widgets), the same endpoint works directly. It’s a single authenticated GET with CORS open to any origin.

GET https://us-central1-curbhero-bd084.cloudfunctions.net/getEmbedStatus
Authorization: Bearer <Firebase ID token>

200 OK
{
  "user": { "uid": "abc", "fullName": "Priya K.", "email": "p@example.com" },
  "households": [
    {
      "id": "h_1207",
      "addressLine1": "1207 Grovewood Ln",
      "locationLabel": "Eagle, ID, 83616",
      "subscriptionStatus": "active",
      "services": [{ "type": "trash", "frequency": "weekly", "dayOfWeek": 2 }],
      "todayPickup": {
        "status": "taken_out",
        "driverFirstName": "Marcus",
        "takeOutPhotoUrl": "https://storage.googleapis.com/...?...",
        "bringBackPhotoUrl": null
      }
    }
  ],
  "fetchedAt": "2026-05-19T14:42:30.000Z"
}
  • Auth. Pass a Firebase ID token in theAuthorization: Bearer … header. Tokens are ~1 hour validity; refresh in the popup flow or callauth.currentUser.getIdToken(true) on the Curb Hero origin.
  • Photo URLs are signed Cloud Storage URLs valid for one hour. The endpoint signs them server-side so your code never needs the Firebase SDK or Storage rules knowledge.
  • Today.The endpoint computes “today” in UTC, matching how Curb Hero materializes scheduled pickups internally.

07

Security notes

  • No cookies.The SDK stores the resident’s ID token in sessionStorage, scoped by partner ID. The token clears when the tab closes.
  • No long-lived auth on the partner origin. Firebase ID tokens are ~1 hour. After expiry the embed automatically reverts to its signed-out state and asks the resident to sign in again.
  • Origin-checked postMessages. The embed only accepts auth messages from https://curbherotrash.com, and only consumes the idToken field. Anything else is dropped.
  • Encapsulated styles. The widget brings its own CSS inline so it cannot collide with host stylesheets. It does not use shadow DOM (so accessibility tooling on the partner site still sees the panel content) but it scopes every style declaration to its container.
  • Idempotent. Drop multiple copies of the script — only the first call mounts. (Internal flag: window.__curbheroEmbedLoaded.)

08

Get a partner ID

No waiting on us. Enter your organization, an admin email, and the domain you’ll embed on, and we’ll mint your partner ID right here — then hand you the snippet, ready to paste. The ID isn’t a secret (it lives in the embed tag); it just attributes signups to you.

Instant — no waiting. The ID isn’t a secret; it just attributes signups to you.

Need partner OAuth (so residents single-sign-on against your identity provider), a vanity ID, or anything else? Email landon@curbherotrash.com or see the live demo.