Saltar al contenido

Especificación · v1

Spaidoo Connector API v1

El contrato público entre un conector y Spaidoo. Los números de sección son los mismos en inglés y en castellano: la comprobación de conformidad y el asistente enlazan a ellos.

Estado: v1 (2026-09-30). La especificación pública de cómo cualquier plataforma de e-commerce se conecta con Spaidoo.


1 Qué es un conector

Spaidoo da a una tienda online un widget (guardar para más tarde, listas, avisos de bajada de precio, búsqueda) y un conjunto de acciones de comunicación (mailing, push, posts). El widget funciona en cualquier web con una sola etiqueta <script>. Un conector es la pieza de software que vive dentro de una plataforma de tienda (un módulo, una app, un plugin, un backend headless…) y le permite a Spaidoo hacer lo que un script solo no puede:

CapacidadQué añade
catalog.feedSpaidoo conoce los productos de la tienda (precio, stock, imágenes) — obligatoria
catalog.changesLos cambios de precio y de stock llegan a Spaidoo en segundos en lugar de en la siguiente lectura diaria
storefront.saveAnchorsEl conector imprime los botones de guardar en el tema (§7); el admin de Spaidoo le indica al comerciante que vienen del conector
identityUn cliente con sesión iniciada en la tienda conserva sus productos guardados en todos sus dispositivos, sin cuenta de Spaidoo
customersSpaidoo puede pedir a la tienda el email (y el consentimiento de marketing) de un cliente identificado cuando el comerciante lo necesita
customerEraseBorrar un cliente en la tienda borra sus datos en Spaidoo para esa tienda
wishlistImportLas listas de una extensión de wishlist anterior se traen a Spaidoo
pushLas notificaciones web push se sirven desde el propio dominio de la tienda
domainVerificationLa tienda demuestra que es dueña de su dominio sin cambios de DNS

Regla del contrato: Spaidoo decide qué hacer a partir de las capacidades que un conector declara, nunca a partir del nombre de su plataforma. Cualquier plataforma puede construir un conector; el campo platform es solo informativo.

Un conector puede implementar cualquier subconjunto, siempre que incluya catalog.feed.


2 Convenciones

  • URL base de las llamadas conector → Spaidoo: https://api-plugin.spaidoo.com/connector/v1
  • JSON de entrada y de salida, UTF-8. Content-Type: application/json.
  • Los tiempos son segundos Unix (enteros) salvo que un campo diga otra cosa.
  • Los ids de la tienda (id de cliente, id de producto) son strings, aunque la plataforma use números.
  • El dinero es un string decimal con punto ("19.90") más un código ISO 4217.
  • Los idiomas son códigos ISO 639-1 (es, ca, en); las monedas, ISO 4217 (EUR).
  • Compatibilidad hacia delante: las dos partes DEBEN ignorar los campos que no conocen. Dentro de v1 pueden aparecer campos opcionales nuevos y capacidades nuevas. Todo lo que rompa un conector existente va a /connector/v2.
  • Errores de Spaidoo: estado HTTP + cuerpo { "errorCode": "SOME_CODE", "message": "human text" }. 401/403 significan que la API key ya no es válida (el conector debe mostrar "desconectado"). 429 lleva Retry-After. Cualquier 5xx o fallo de red significa "prueba más tarde": un conector DEBE seguir funcionando (la tienda nunca debe romperse) cuando Spaidoo no responde.

2.1 Los dos secretos

SecretoQué esPara qué se usa
API keyString opaco emitido en el emparejamientoAuthorization: Bearer <apiKey> en cada llamada conector → Spaidoo
Identity secret32 bytes aleatorios, base64url, emitido por SpaidooFirmas HMAC: aserciones de identidad (§5) y verificación de las llamadas Spaidoo → tienda (§6)

Los dos son secretos de la tienda. DEBEN guardarse en el servidor y no imprimirse nunca en las páginas.


3 Emparejamiento

Una tienda se conecta con OAuth 2 y PKCE (RFC 6749, RFC 7636), como app de conector registrada. La API key nunca pasa por un navegador ni por una URL.

3.1 Registra tu conector

Registra el conector una vez en el área de partners de developers.spaidoo.com (Mis conectores) y obtienes su client_id (sc_…, público). Hay dos tipos de app:

redirectModeParaVuelta (redirect_uri)
fixedUn conector alojado por ti o por la plataforma (el admin del comerciante no está en el dominio de la tienda)Una de las redirectUris que registras, coincidencia exacta
shopUn conector instalado en cada tienda (un módulo, un plugin)Una URL https en el dominio de la tienda que se conecta (o un subdominio suyo); no hay nada que registrar

serverDomains: los dominios donde pueden vivir tus rutas de servidor (urls.customers) además de los de la tienda (§4.1).

platform es texto libre que cumple ^[a-z0-9-]{2,32}$ (my-platform, my-headless-shop, acme-commerce…). Una app suspendida no conecta tiendas nuevas; las ya conectadas siguen funcionando.

3.2 El flujo

  1. Tu servidor genera un state aleatorio y un code_verifier (43–128 caracteres de [A-Za-z0-9-._~]), guarda los dos y calcula code_challenge = base64url(SHA-256(code_verifier)) sin relleno.
  2. Tu página de admin abre un popup (window.open, unos 460×780, con el clic del comerciante) en:
    https://admin.spaidoo.com/connect?client_id=sc_…&redirect_uri=…&state=…
      &code_challenge=…&code_challenge_method=S256
      &domain=shop.example.com&name=<shop name>&email=<merchant email>
      &mode=signup|login&verify=<urls.verify>&lang=<iso>
    domain es obligatorio; name, email, mode (la pestaña que se abre primero), verify (solo con domainVerification) y lang son opcionales.
  3. Spaidoo comprueba el client_id y la redirect_uri. Si alguno no es válido muestra el error y nunca redirige.
  4. El comerciante se registra o inicia sesión y acepta los términos. Una tienda que ya existe en el directorio de Spaidoo solo se entrega cuando se demuestra su dominio (§3.3).
  5. Spaidoo envía el popup a redirect_uri?code=…&state=…. Si el comerciante cancela: redirect_uri?error=access_denied&state=…; una petición mal formada: error=invalid_request.
  6. Tu servidor comprueba state y canjea el código en menos de 60 segundos (un código sirve para un solo canje):

POST /pairing/token — la única llamada sin API key:

JSON
{ "client_id": "sc_…", "code": "…", "code_verifier": "…", "redirect_uri": "<la misma>" }

→ 200 { "apiKey": "…", "shopId": "…" }. Errores: 400 OAUTH_INVALID_GRANT (código desconocido, caducado o ya usado, o client_id, redirect_uri o code_verifier no coinciden), 403 CONNECTOR_APP_SUSPENDED.

  1. Guarda la key en el servidor y llama a POST /handshake (§4.1). Después, la página de redirect_uri avisa a tu página de admin (postMessage a window.opener, solo a tu propio origen y nunca con la key) y cierra el popup; tu admin muestra la tienda conectada.

3.3 Una tienda del directorio de Spaidoo

Cuando el dominio ya pertenece a una tienda del directorio de Spaidoo y la petición lleva verify, Spaidoo primero envía el popup a:

redirect_uri?state=…&verification_code=spaidoo-verify-…&resume=https://admin.spaidoo.com/connect?…

Tu vuelta comprueba state, guarda el código para que urls.verify lo sirva (§4.9), conserva el code_verifier y envía el popup a resume tal como ha llegado. Spaidoo lee el código en urls.verify y sigue; el código llega después a la misma vuelta, con el mismo state. Sin verify, el comerciante demuestra el dominio a mano con un archivo (§4.9).

Una tienda nueva se conecta directamente; su conector demuestra el dominio después, con su key, antes de publicar (§4.9).


4 Conector → Spaidoo

Todas las llamadas: Authorization: Bearer <apiKey>.

4.1 POST /handshake — declarar qué puede hacer el conector

Llámalo después del emparejamiento, después de cada actualización del conector y siempre que cambie una URL o una capacidad. Es idempotente.

JSON
{
  "contractVersion": 1,
  "platform": { "id": "my-platform", "version": "3.2.0" },
  "connector": { "version": "2.0.0" },
  "storeUrl": "https://shop.example.com",
  "capabilities": {
    "catalog.feed": true,
    "catalog.changes": true,
    "identity": true,
    "customers": true,
    "customerErase": true,
    "wishlistImport": false,
    "push": true,
    "domainVerification": true,
    "storefront.saveAnchors": true
  },
  "urls": {
    "identity": "https://shop.example.com/spaidoo/identity",
    "login": "https://shop.example.com/login?back={back}",
    "customers": "https://shop.example.com/spaidoo/customers",
    "verify": "https://shop.example.com/spaidoo/verify",
    "pushWorker": "https://shop.example.com/spaidoo/push-sw.js"
  },
  "storefront": {
    "pushScope": "/spaidoo-push/",
    "domains": ["shop.example.com", "my-shop.platform.example"],
    "excludePaths": ["^/checkout", "^/cart"]
  },
  "identitySecretFingerprint": "<sha256 hex of the identity secret it holds, or null>"
}
  • Toda URL DEBE ser https. Las URL de escaparate (identity, login, pushWorker), que el widget llama desde la página del cliente, y verify, que demuestra el dominio, DEBEN estar en uno de los dominios de la tienda. Las URL de servidor (customers), que Spaidoo llama firmadas, PUEDEN estar también en uno de los serverDomains de la app con la que se conectó la tienda (§3.1). Una URL solo es obligatoria para una capacidad declarada que la necesite.
  • storefront.domains: otros dominios en los que se sirve el escaparate (un subdominio de la plataforma y el dominio propio de la tienda, por ejemplo), solo el nombre de host. El widget funciona en todos; para publicar sigue haciendo falta la prueba del dominio de storeUrl (§4.9).
  • login DEBE contener el marcador literal sin codificar {back}; el widget lo sustituye por la página a la que volver.
  • storefront.excludePaths: expresiones regulares (sin distinguir mayúsculas y minúsculas) sobre la ruta + query donde el widget no debe ejecutarse nunca (checkout, pago). El conector DEBERÍA además no imprimir el script ahí (§7); esta lista es la red de seguridad.
  • identitySecretFingerprint: permite que un conector que ha perdido su secreto (reinstalado, restaurado desde una copia de seguridad) obtenga uno nuevo por su cuenta. null = no tiene ninguno.

Respuesta 200:

JSON
{
  "shopId": "7f3c…",
  "contractVersion": 1,
  "capabilities": { "catalog.changes": true, "identity": true, "…": "…" },
  "identitySecret": "b64url…"
}
  • capabilities son las que Spaidoo ha aceptado. El conector debe comportarse según esta lista. Una capacidad se rechaza (se omite, no es un error) cuando Spaidoo la tiene desactivada; customers y wishlistImport se rechazan sin identity.
  • identitySecret está presente cuando el conector debe guardar uno nuevo (§4.2).

Errores: 400 CONNECTOR_CATALOG_REQUIRED (sin catalog.feed), 400 CONNECTOR_URL_INVALID con field (falta la URL de una capacidad declarada, no es https en un dominio permitido, o login sin el {back} sin codificar), 400 CONNECTOR_EXCLUDE_PATH_INVALID.

4.2 Entrega y rotación del identity secret

  • Spaidoo emite el secreto en el handshake y lo devuelve en las respuestas de /handshake y /status hasta que el conector lo confirma.
  • POST /identity-secret/ack { "fingerprint": "<sha256 hex of the secret>" } → 200 { "acknowledged": true } (false si no es el secreto actual). El conector DEBE confirmar cada secreto que recibe, después de guardarlo.
  • POST /identity-secret/rotate → 200 { "identitySecret": "…" }: el comerciante pide uno nuevo.
  • Periodo de gracia: después de una rotación, Spaidoo sigue aceptando aserciones firmadas con el secreto anterior y sigue firmando sus propias llamadas con el secreto anterior hasta que se confirma el nuevo, y durante 10 minutos después de la confirmación. Un conector que pierde una respuesta sigue funcionando.

4.3 GET /status — latido y estado

Llámalo cuando el comerciante abra el panel del conector (y, si no, como mucho cada pocos minutos).

JSON
{
  "shopId": "7f3c…",
  "identitySecret": "b64url…",
  "store": {
    "name": "Mi tienda", "plan": "free", "status": "active", "verified": true,
    "scriptUrl": "https://widget.spaidoo.com/loaders/7f3c….js",
    "adminUrl": "https://admin.spaidoo.com",
    "widget": { "mode": "live" },
    "stats": { "…": "…" },
    "feeds": [ { "language": "es", "url": "…", "lastReadAt": 1759219200, "products": 412, "status": "ok" } ]
  }
}

scriptUrl es lo que el conector imprime en la tienda (§7). Puede cambiar: usa siempre el último.

connector es lo que Spaidoo tiene del último handshake (contractVersion, platform, version, capabilities aceptadas, urls, storefront), o null antes del primero. Nunca el secreto.

4.4 POST /uninstall — {} → 204

El conector se está desinstalando. Spaidoo deja de leer el catálogo, oculta los productos y apaga el loader. Los datos se conservan para que una reinstalación continúe donde se quedó.

4.5 Catálogo — catalog.feed (obligatoria)

PUT /catalog/feeds

JSON
{ "feeds": [ { "language": "es", "url": "https://shop.example.com/spaidoo/feed?lang=es" } ] }

La lista completa: un idioma que falte en ella se elimina. Spaidoo lee cada feed aproximadamente una vez al día.

POST /catalog/feeds/read → 202 { "queued": true } — "leer ahora" (p. ej. un botón en el panel). 429 { "queued": false, "retryInSeconds": n } con Retry-After si se vuelve a pedir demasiado pronto (3 minutos).

El feed es un feed de productos de Google Merchant (RSS 2.0 o Atom, espacio de nombres g: opcional) con un <item>/<entry> por variante:

CampoObligatorioNotas
g:id✅Id de la variante
g:item_group_idsi hay variantesEl id de producto para Spaidoo (una fila por producto). Sin él, g:id es el id de producto
g:title, g:link✅Localizados; link es la URL canónica del producto
g:price✅"19.90 EUR": la moneda base de la tienda
g:sale_priceMisma moneda que g:price
g:availability✅in_stock · out_of_stock · preorder · backorder
g:image_link, g:additional_image_link
g:description, g:brand, g:product_type (A > B > C), g:color, g:size, g:mpn
spaidoo:price currency="USD"Espacio de nombres https://spaidoo.com/ns/feed/1. Precio exacto en cada una de las otras monedas en las que vende la tienda (repetible)
spaidoo:sale_price currency="USD"

El id de producto es el que se usa en el resto de este contrato (data-sp-id, catalog.changes, importación de wishlists).

Mientras se genera un feed grande, responde 202 (o 503/429) con Retry-After: <seconds>: Spaidoo vuelve entonces y no lo trata como un catálogo vacío. Cualquier otro error conserva el catálogo anterior.

4.6 Cambios de catálogo — catalog.changes

Cuando un producto cambia en la tienda, el conector se lo dice a Spaidoo en ese momento. Los avisos de bajada de precio salen en minutos en lugar de en la siguiente lectura diaria. La lectura diaria del feed se mantiene como conciliación.

POST /catalog/changes

JSON
{
  "changes": [
    {
      "type": "product.updated",
      "id": "123",
      "at": 1759219200,
      "price": { "amount": "24.90", "currency": "EUR" },
      "salePrice": { "amount": "19.90", "currency": "EUR" },
      "prices": [ { "currency": "USD", "price": "27.90", "salePrice": "21.90" } ],
      "availability": "in_stock",
      "variants": [
        { "sku": "123-1", "price": "24.90", "salePrice": "19.90", "availability": "in_stock" },
        { "sku": "123-2", "price": "24.90", "salePrice": null, "availability": "out_of_stock" }
      ]
    },
    { "type": "product.deleted", "id": "456", "at": 1759219230 }
  ]
}
  • id es el id de producto del feed (item_group_id, o id sin variantes).
  • product.updated lleva solo lo que puede cambiar sin tocar la estructura del catálogo: price, salePrice (null = sin oferta), prices, availability, variants. Todos los campos son opcionales; un campo presente sustituye al valor guardado.
  • variants: para un producto con variantes, las variantes que han cambiado, por sku (el g:id del feed), con los importes en la moneda de price. Las bajadas de precio se miden sobre la variante más barata, igual que con el feed. Las variantes que no se envían conservan sus valores.
  • product.deleted: el producto ya no se vende (borrado o desactivado).
  • at es cuándo ocurrió el cambio en la tienda. Spaidoo ignora un cambio más antiguo que el que ya ha aplicado a ese producto, así que los reintentos y la entrega desordenada son seguros.
  • Los importes son strings decimales con 4 decimales como máximo ("24.90"). price y salePrice deben estar en la misma moneda; los importes de las variantes también están en esa moneda. Los cambios en una moneda distinta de la moneda base de un idioma no tocan los precios de ese idioma.
  • availability: in_stock · out_of_stock · preorder · backorder.
  • Un at en el futuro se toma como ahora.
  • Hasta 100 cambios por petición y 60 peticiones por minuto por tienda (429 + Retry-After). El conector DEBERÍA agrupar las ráfagas (una edición masiva de precios de 2 000 productos son 20 peticiones, no 2 000).
  • 409 CONNECTOR_CAPABILITY_MISSING si el handshake no declaró catalog.changes.
  • Durante las 24 horas siguientes a un cambio, el feed no sobrescribe los precios ni el stock de ese producto (un feed generado antes del cambio lo desharía); sí actualiza el resto.
  • Los productos nuevos, y los cambios de título, enlace, imágenes o categorías, llegan con la siguiente lectura del feed. Un conector puede llamar a POST /catalog/feeds/read después de una importación masiva.

Respuesta 202 (los cambios se aplican de forma asíncrona, en segundos):

JSON
{ "accepted": 1, "unknown": ["456"] }

unknown lista los ids que Spaidoo todavía no tiene; programa una lectura del feed. La entrega es best effort: si falla, reintenta con backoff durante 1 hora como máximo y luego descarta — la lectura diaria del feed lo corrige todo.

4.7 POST /customers/erase — customerErase

{ "customerId": "123" } → 204. La tienda ha borrado a este cliente (o ha recibido una solicitud de supresión del RGPD). Spaidoo borra la lista del cliente vinculada a esta tienda y desvincula de ella cualquier cuenta de Spaidoo. No hace falta enviar a los invitados (clientes sin cuenta).

4.8 POST /wishlists/import — wishlistImport

JSON
{
  "customers": [
    { "customerId": "123", "lists": [
      { "id": "1", "name": "My wishlist", "isDefault": true, "products": ["123", "789"] }
    ] }
  ]
}

Hasta 50 clientes por llamada, 100 listas por cliente y 1 000 productos por lista. Los productos son ids de producto del feed. Idempotente: importar la misma lista dos veces no la duplica.

4.9 Verificación de dominio — domainVerification

  • POST /domain-verification → { "verified": false, "verificationCode": "spaidoo-verify-…", "domain": "shop.example.com" }. El conector guarda el código y lo sirve en urls.verify como text/plain, sin nada más en el cuerpo.
  • urls.verify debe estar en el dominio que se demuestra (o en su www.) y no debe contener el código. Spaidoo no sigue redirecciones.
  • POST /domain-verification/check → { "verified": true }. Spaidoo ha pedido urls.verify y ha encontrado el código.
  • Sin la capacidad, el comerciante puede verificar igualmente subiendo el código como https://<domain>/spaidoo-verify.txt.

4.10 Ajustes del widget desde el panel del conector (opcional)

Un conector puede dejar que el comerciante cambie algunos ajustes del widget sin salir del back-office. Son las únicas escrituras que puede hacer un conector:

LlamadaCuerpo
POST /widget/state{ "mode": "off" | "preview" | "live" } — 403 DOMAIN_NOT_VERIFIED para live en un dominio sin verificar
POST /widget/placement{ "desktop"?: {…}, "mobile"?: {…} } cada uno { position?: "bottom-left"|"bottom-right", orientation?: "vertical"|"horizontal", showButton?: bool } — 409 si el comerciante ha definido una posición personalizada en Spaidoo
POST /widget/appearance{ accent?, iconColor?|null, icon?: "bookmark"|"heart", productCircle?, satelliteColor?, satelliteIconColor? } (colores #rrggbb)
POST /widget/search{ showSearchButton?: bool, chatAI?: bool }

4.11 POST /sso — abrir el admin de Spaidoo con la sesión ya iniciada

{ "intent": "panel" } → { "url": "…" } (de un solo uso, 5 minutos). Intents: panel, widget-setup, spaidoo-search, providers, email, posts, carousels, push, automations. Los intents desconocidos caen en panel.


5 Identidad — identity

Objetivo: un cliente con sesión iniciada en la tienda ve los mismos productos guardados en todos sus dispositivos y puede vincularlos a una cuenta de Spaidoo. Mientras la tienda tiene un conector con identidad, manda la sesión de la tienda: Spaidoo nunca pide al cliente que inicie sesión en la tienda en nombre de Spaidoo.

5.1 La ruta de identidad (urls.identity)

El widget la llama desde la tienda, en el mismo origen, con las cookies de la tienda:

GET <urls.identity>?anon=<uuid>

RespuestaCuándo
200 { "assertion": "<assertion>" }Hay un cliente con sesión iniciada
200 { "anonymous": true }No hay nadie con sesión iniciada (los invitados cuentan como nadie)
410El conector está desconectado

Cabeceras: Cache-Control: no-store, private y Vary: Cookie. NUNCA debe guardarla en caché una caché de páginas ni un CDN.

5.2 La aserción

payload   = base64url( JSON ), no padding
signature = base64url( HMAC-SHA256( key = identity secret as UTF-8 text, data = payload ) )
assertion = payload + "." + signature

JSON:

JSON
{ "v": 1, "shopId": "7f3c…", "customerId": "123", "email": "", "anonId": "<the anon query param or null>", "iat": 1759219200, "exp": 1759219260, "jti": "<32 hex chars, random>" }
  • exp - iat ≤ 60 s. Spaidoo tolera 30 s de desfase de reloj.
  • jti es de un solo uso.
  • anonId repite ?anon= si es un UUID; si no, null.
  • email DEBE ser "": Spaidoo no guarda los emails de los clientes de la tienda (ver §6.2).

5.3 La pista de sesión (opcional, recomendada)

Para que el widget detecte un inicio o un cierre de sesión sin preguntar en cada página, el conector imprime en SpaidooConfig.shopSession (§7):

"" cuando no hay nadie con sesión iniciada; si no, los primeros 16 caracteres hex de HMAC-SHA256(identity secret, "session:" + customerId).

Es una pista, no una prueba: el widget solo la usa para decidir cuándo volver a llamar a la ruta de identidad.

5.4 Inicio de sesión

Cuando el cliente quiere conservar su lista y no tiene la sesión iniciada, el widget lo envía a urls.login con {back} sustituido por la página en la que estaba, codificada para URL. Después del inicio de sesión, la tienda DEBE redirigirlo de vuelta a ella (solo en el mismo dominio).


6 Spaidoo → tienda (llamadas firmadas)

6.1 Firma

Cada llamada que Spaidoo hace a una URL del conector (salvo las públicas verify y feed) lleva:

CabeceraValor
X-Spaidoo-TimestampSegundos Unix
X-Spaidoo-Nonce32 caracteres hex aleatorios
X-Spaidoo-Signaturebase64url( HMAC-SHA256( identity secret, timestamp + "." + nonce + "." + rawBody ) )

El conector DEBE:

  1. rechazar un timestamp a más de 300 s de su reloj (401);
  2. comparar la firma en tiempo constante sobre el cuerpo en bruto (401);
  3. DEBERÍA recordar los nonces durante 600 s y rechazar uno repetido (401).

6.2 POST <urls.customers> — customers

JSON
{ "customerIds": ["123", "124"] }

Hasta 500 ids. Responde 200:

JSON
{ "customers": [ { "customerId": "123", "email": "ana@example.com", "marketingConsent": true } ] }
  • Omite los clientes que ya no existen, los invitados y los borrados.
  • marketingConsent: true el cliente aceptó los emails de marketing de la tienda (alta en la newsletter), false no los aceptó, null la plataforma no lo sabe.
  • Spaidoo usa la respuesta en ese momento (una exportación, la audiencia de una campaña, una sincronización con la herramienta de email del comerciante) y no guarda los emails.
  • Responde en menos de 5 s. 410 si está desconectado.

Qué hace Spaidoo con marketingConsent cuando el comerciante sincroniza con una herramienta de email (Klaviyo, Mailchimp…): true → suscrito al email marketing; false/null → perfil sin suscripción de marketing (solo flujos transaccionales).


7 Tienda

Lo que el conector imprime en las páginas de la tienda (en todas salvo las excluidas):

HTML
<script data-spaidoo-config='{"currency":"EUR","shopSession":"","push":{"swUrl":"https://shop.example.com/spaidoo/push-sw.js","swScope":"/spaidoo-push/"}}'>
  (function (s) { window.SpaidooConfig = Object.assign(window.SpaidooConfig || {}, JSON.parse(s.getAttribute('data-spaidoo-config'))); })(document.currentScript);
</script>
<script src="{scriptUrl}" async></script>

window.SpaidooConfig — todas las claves son opcionales:

ClaveValor
currencyCódigo ISO de la moneda en la que navega el visitante. El widget muestra los precios en ella (desde spaidoo:price)
shopSessionPista de sesión (§5.3)
previewtrue solo para el personal de la tienda mientras el widget está en modo preview
push{ swUrl, swScope } cuando se declara push
  • No imprimas nunca el script en las páginas de checkout o de pago.
  • Una caché de página completa debe guardar una copia por moneda (como ya hace con los precios).

Botones de guardar (opcional): donde el tema deba mostrar el icono de guardar de Spaidoo, imprime un anclaje vacío. El widget dibuja el icono y gestiona el clic y el estado.

HTML
<span data-spaidoo-save
      data-sp-context="product"            <!-- "product" (product page) or "list" (cards) -->
      data-sp-id="123"                     <!-- product id of the feed -->
      data-sp-url="https://shop.example.com/p/123"
      data-sp-name="Blue shirt"            <!-- optional -->
      data-sp-image="https://…/123.jpg"></span>  <!-- optional -->

Dale estilo con las propiedades CSS personalizadas --sp-save-*; el widget no añade ningún layout propio.

Enlace a la biblioteca (opcional): cualquier elemento con data-spaidoo-library abre la lista del cliente (p. ej. una entrada del menú de la cuenta).

7.1 Service worker de push — push

El web push se registra en el dominio de la tienda, así que el conector sirve el service worker de Spaidoo en urls.pushWorker:

  • Content-Type: application/javascript, Cache-Control: no-cache.
  • Service-Worker-Allowed: <pushScope> cuando el alcance está fuera del directorio del propio worker. Un worker servido dentro de su alcance (/apps/spaidoo/push-sw.js con pushScope /apps/spaidoo/, detrás del proxy de apps de una plataforma, por ejemplo) no necesita la cabecera.
  • Cuerpo: el worker de referencia publicado en https://cdn.spaidoo.com/connector/v1/push-sw.js (cópialo tal cual; no lo cargues con importScripts desde otro origen).
  • Recibe mensajes solo de datos { title, body, link, icon, image, badge, shopId }.

8 Conformidad

Un conector es compatible con Spaidoo v1 cuando la comprobación de conformidad en línea pasa sin fallos en todas las capacidades que declara.

Conecta una instalación de prueba de tu plataforma con tu conector (cualquier tienda accesible desde internet: un dominio de staging, o un túnel como ngrok o Cloudflare Tunnel para una local) y lanza la comprobación desde la página de desarrolladores con la API key de esa tienda. Spaidoo ya tiene el handshake y el identity secret, así que ningún secreto se pega en ninguna parte.

La misma comprobación está disponible para tus propias herramientas:

  • POST /connector/v1/conformance { "shop"?: "<product page>", "checkout"?: "<cart page>", "cookie"?: "<Cookie header of a signed-in test customer>", "customer"?: "<customer id>" } → 202 { "runId": "…" }. Las páginas deben estar en los dominios de la tienda. Diez ejecuciones por hora y tienda (429 + Retry-After).
  • GET /connector/v1/conformance/{runId} → { "status": "running" } y luego { "status": "done", "conformant": true|false, "counts": {…}, "results": [ { "capability", "title", "status": "pass"|"fail"|"warn"|"skip", "detail"?, "spec"? } ] }. Se conserva una hora.

Comprueba el handshake, los feeds y sus artículos, la ruta de identidad y sus aserciones, la ruta firmada customers (nonce, firma, cuerpo en bruto, antigüedad, límites), la ruta de verificación, el service worker de push y la tienda (loader, SpaidooConfig, anclajes de guardado, nada en el checkout). Las llamadas del conector a Spaidoo (catalog.changes, customerErase, wishlistImport) se listan con cómo comprobarlas a mano.

9 Cambios

FechaCambio
2026-09-30Primer borrador
2026-09-30Emparejamiento con OAuth 2 + PKCE y apps de conector registradas (§3); URL de servidor en los dominios de la app y storefront.domains (§4.1); worker de push dentro de su alcance (§7.1)