Relay docs
Implementation

Identity Verification

Prove which shop is using your app by signing a short-lived identity with your Relay Secret.

Relay needs to know which Shopify shop is loading the embed or widget. Instead of handing Relay your Shopify app secret, your own server signs each shop's identity with a Relay Secret that Relay issues to you. Relay verifies the signature with the same secret. Your Shopify client secret never leaves your infrastructure.

How it works

  1. Relay generates a per-app Relay Secret (rlys_…). You store it on your server as RELAY_SECRET.
  2. On each page load, your server — which already knows the authenticated shop — computes an HMAC signature over the shop domain and a timestamp.
  3. Your page exposes that signature to the embed/widget as window.RelayEmbed.identity.
  4. Relay verifies the signature and issues a short-lived session.

The signature is only valid for 5 minutes, so sign it fresh on every page load. The embed exchanges it for a longer-lived Relay session immediately on mount.

Get your Relay Secret

In the Relay admin, open Settings → Apps → your app → Relay Secret and click Generate Relay Secret. Copy the value into your server environment:

RELAY_SECRET=rlys_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The Relay Secret is server-only. Never expose it to the browser, commit it, or send it to Relay. Only the derived signature is safe to put on the page.

The signature

The canonical message is:

v1:{shop}:{ts}
  • shop — the *.myshopify.com domain (e.g. acme.myshopify.com)
  • ts — current Unix time in seconds
signature = hex( HMAC_SHA256(RELAY_SECRET, "v1:{shop}:{ts}") )

Expose the result as a global the embed reads:

<script>
  window.RelayEmbed = {
    identity: { shop: "acme.myshopify.com", ts: 1781190000, signature: "a1b2c3…" },
  }
</script>

React Router / Remix (Shopify app template)

If you use the Shopify React Router app template, sign the identity right in your app route loader — no extra API endpoint needed. The loader already authenticates the shop, so session.shop is verified.

// app/routes/app.tsx  (your authenticated app layout route)
import { createHmac } from "node:crypto"
import type { LoaderFunctionArgs } from "react-router"
import { authenticate } from "../shopify.server"

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { session } = await authenticate.admin(request)

  const ts = Math.floor(Date.now() / 1000)
  const signature = createHmac("sha256", process.env.RELAY_SECRET!)
    .update(`v1:${session.shop}:${ts}`)
    .digest("hex")

  return {
    relay: {
      embedToken: process.env.RELAY_EMBED_TOKEN, // public
      identity: { shop: session.shop, ts, signature },
    },
  }
}

Then render the identity into the page before embed.js runs:

import { useLoaderData } from "react-router"

export default function App() {
  const { relay } = useLoaderData<typeof loader>()
  return (
    <>
      <script
        // biome-ignore lint: trusted, server-generated values
        dangerouslySetInnerHTML={{
          __html: `window.RelayEmbed = ${JSON.stringify({ identity: relay.identity })}`,
        }}
      />
      <script src="https://app.superrelay.ai/embed/embed.js" data-token={relay.embedToken} />
      <div id="relay-helpdesk" />
      <div id="relay-feature-requests" />
      {/* …your app… */}
    </>
  )
}

Because the loader runs on every navigation to the app, the signature is always fresh.

PHP

<?php
// $shop comes from your verified Shopify session.
$shop = $session->getShop();          // e.g. "acme.myshopify.com"
$ts = time();
$signature = hash_hmac('sha256', "v1:{$shop}:{$ts}", getenv('RELAY_SECRET'));

$identity = json_encode([
  'shop' => $shop,
  'ts' => $ts,
  'signature' => $signature,
]);
?>
<script>
  window.RelayEmbed = { identity: <?php echo $identity; ?> };
</script>
<script src="https://app.superrelay.ai/embed/embed.js" data-token="<?php echo getenv('RELAY_EMBED_TOKEN'); ?>"></script>
<div id="relay-helpdesk"></div>

Any language

The algorithm is plain HMAC-SHA256 — implement it with your standard library:

StepValue
Messagev1:{shop}:{ts}
Keyyour RELAY_SECRET
HashSHA-256
Outputlowercase hex

Set window.RelayEmbed.identity = { shop, ts, signature } before the Relay script loads, and you're done.

Rotating the secret

You can rotate the Relay Secret any time from Settings → Apps. Rotation invalidates the previous secret immediately, so update RELAY_SECRET on your server before the next request.

Troubleshooting

  • Signature verification failed — the message or secret don't match. Check the exact format v1:{shop}:{ts} and that RELAY_SECRET matches the admin.
  • Signature timestamp is outside the allowed windowts must be within 5 minutes of now, in seconds (not milliseconds). Sign on each page load.
  • Relay Secret is not configured — generate one in the Relay admin first.
  • Read-only embedwindow.RelayEmbed.identity is missing or malformed; the embed still shows public surfaces (like changelog) but can't authenticate.

On this page