Documentação · Guias

Sessões alojadas, passo a passo

O seu backend cria uma sessão e manda a pessoa para uma página própria do Urdume. Lá ela completa os dados (também a partir de documentos, com IA), emite o passaporte, leva o QR Code e volta ao seu sistema. Sem conta Urdume para cada pessoa e sem guardar nada do Urdume na sua base de dados.

Como funciona

  1. Na ficha do produto do seu painel, a pessoa carrega em "Passaporte digital".
  2. O seu servidor cria a sessão (POST /passport_sessions) com a referência do produto e quem vai preencher, e redireciona para "url".
  3. Na página do Urdume a pessoa completa os dados, revê, confirma e emite. Descarrega o QR Code (SVG ou PNG) e, se pediu números de série, a lista dos artigos.
  4. Ao carregar em "Concluir e voltar" regressa ao seu success_url, com o ID da sessão.
  5. O seu servidor confirma a sessão pela API (ou recebe o webhook) e o passaporte fica sempre em /r/{empresa}/{referência}.

1. Criar a sessão no seu servidor

"operator" identifica a pessoa que vai preencher (já autenticada no seu sistema): fica no registo de atividade e nas versões que emitir. Se a referência ainda não existir no Urdume, o produto é criado (aí "name" é obrigatório e "data" pré-preenche os campos).

POST /passport_sessions

curl

curl https://urdume.site/api/v1/passport_sessions \
  -H "Authorization: Bearer $URDUME_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: dpp-produto-100-ana-1" \
  -d '{
    "reference": "TS-100",
    "name": "T-shirt básica",
    "operator": {"name": "Ana Silva", "email": "ana@loja.exemplo.pt"},
    "success_url": "https://loja.exemplo.pt/admin/produtos/100?dpp={SESSION_ID}",
    "cancel_url": "https://loja.exemplo.pt/admin/produtos/100",
    "metadata": {"produto_id": "100"}
  }'

PHP

<?php
// No seu backend, quando a pessoa carrega em "Passaporte digital" na ficha do produto.
$payload = [
    'reference' => $produto['referencia'],
    'name' => $produto['nome'],
    'operator' => ['name' => $utilizador['nome'], 'email' => $utilizador['email']],
    'success_url' => 'https://loja.exemplo.pt/admin/produtos/' . $produto['id'] . '?dpp={SESSION_ID}',
    'cancel_url' => 'https://loja.exemplo.pt/admin/produtos/' . $produto['id'],
    'metadata' => ['produto_id' => (string) $produto['id']],
];
$ch = curl_init('https://urdume.site/api/v1/passport_sessions');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 15,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('URDUME_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($payload),
]);
$session = json_decode((string) curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status !== 200) {
    throw new RuntimeException($session['error']['message'] ?? 'Erro ao contactar o Urdume');
}
header('Location: ' . $session['url'], true, 303);
exit;

Node

// Express + Node 18+
app.post('/admin/produtos/:id/passaporte', async (req, res) => {
  const produto = await produtos.find(req.params.id);
  const response = await fetch('https://urdume.site/api/v1/passport_sessions', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.URDUME_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      reference: produto.referencia,
      name: produto.nome,
      operator: { name: req.user.name, email: req.user.email },
      success_url: `https://loja.exemplo.pt/admin/produtos/${produto.id}?dpp={SESSION_ID}`,
      cancel_url: `https://loja.exemplo.pt/admin/produtos/${produto.id}`,
      metadata: { produto_id: String(produto.id) },
    }),
  });
  const session = await response.json();
  if (!response.ok) throw new Error(session.error.message);
  res.redirect(303, session.url);
});

Python

# Flask + requests
import os, requests
from flask import redirect

@app.post("/admin/produtos/<int:produto_id>/passaporte")
def criar_passaporte(produto_id):
    produto = produtos.find(produto_id)
    response = requests.post(
        "https://urdume.site/api/v1/passport_sessions",
        headers={"Authorization": f"Bearer {os.environ['URDUME_KEY']}"},
        json={
            "reference": produto.referencia,
            "name": produto.nome,
            "operator": {"name": current_user.name, "email": current_user.email},
            "success_url": f"https://loja.exemplo.pt/admin/produtos/{produto_id}?dpp={{SESSION_ID}}",
            "cancel_url": f"https://loja.exemplo.pt/admin/produtos/{produto_id}",
            "metadata": {"produto_id": str(produto_id)},
        },
        timeout=15,
    )
    session = response.json()
    response.raise_for_status()
    return redirect(session["url"], code=303)
ParâmetroDescrição
reference Obrigatório. A referência do produto no seu sistema.
operator[name] Obrigatório. Quem vai preencher (até 160 caracteres). operator[email] é opcional.
success_url Obrigatório. HTTPS (http://localhost só em testes). {SESSION_ID} é trocado pelo ID da sessão.
cancel_url Opcional. Para onde leva o link "Voltar" sem concluir.
serials Opcional. Números de série das peças (até 5000): os que faltarem são criados ao emitir.
metadata Opcional. Até 20 pares chave/valor, devolvidos na sessão e no webhook (ex.: o ID do produto na loja).
expires_at Opcional. Data Unix entre 30 minutos e 24 horas a partir de agora (por omissão, 24 horas).

2. O que a pessoa vê

  • À esquerda, a sua marca (logótipo, cores e letras de Definições › Marca) e o produto; à direita, os passos.
  • Dados: o formulário do modelo têxtil, com o que já existir pré-preenchido. Pode carregar a ficha técnica e certificados: a IA sugere valores e a pessoa aceita, corrige ou rejeita cada um.
  • Rever e emitir: um resumo e a confirmação "declaro em nome de…". Emite o passaporte, ou uma nova versão se os dados mudaram.
  • Concluído: o QR Code (SVG e PNG), o endereço público, o endereço pela referência, o conteúdo NFC e a lista dos artigos (CSV).

3. No regresso, confirme pela API

Não confie só no endereço de regresso: confirme o estado com GET /passport_sessions/{id}. "outcome" diz o que aconteceu: issued (passaporte novo), version (nova versão) ou unchanged (os dados já estavam publicados).

GET /passport_sessions/{id}

curl

curl https://urdume.site/api/v1/passport_sessions/01JA2… \
  -H "Authorization: Bearer $URDUME_KEY"

{
  "id": "01JA2…",
  "object": "passport_session",
  "status": "completed",
  "outcome": "issued",
  "reference": "TS-100",
  "public_url": "https://urdume.site/r/a-sua-marca/TS-100",
  "passport": "01JA3…",
  "passport_url": "https://urdume.site/p/01JA3…",
  "items_created": 0,
  "operator": {"name": "Ana Silva", "email": "ana@loja.exemplo.pt"},
  "metadata": {"produto_id": "100"}
}

PHP

<?php
// success_url: /admin/produtos/100?dpp=01JA2…  (confirme sempre pela API, não pelo endereço)
$id = $_GET['dpp'] ?? '';
if (!preg_match('/^[0-9A-Z]{26}$/', $id)) {
    exit('Sessão inválida');
}
$ch = curl_init('https://urdume.site/api/v1/passport_sessions/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('URDUME_KEY')],
]);
$session = json_decode((string) curl_exec($ch), true);
if (($session['status'] ?? '') === 'completed') {
    // Guarde (se quiser) o endereço público; a referência já chega para o voltar a montar.
    guardarPassaporte($session['metadata']['produto_id'], $session['public_url']);
}

4. Ou receba o webhook

A pessoa pode fechar o separador sem voltar. Para não depender disso, subscreva o evento passport_session.completed em Programadores › Webhooks: chega logo que o passaporte é emitido, com a sessão completa (incluindo "metadata"). Veja o guia de webhooks para verificar a assinatura.

5. Mostre o passaporte na loja

O endereço pela referência não muda nunca: https://urdume.site/r/a-sua-marca/TS-100 (e /TS-100/{série} para uma peça). Use-o em links, no QR Code e no NFC, ou mostre um resumo na página do produto com o widget.

Erros frequentes

CódigoO que fazer
parameter_missing (operator.name) Indique quem vai preencher a página.
invalid_return_url success_url/cancel_url têm de ser absolutos e HTTPS (http://localhost só em testes).
expires_at_out_of_range Use uma data entre 30 minutos e 24 horas a partir de agora.
product_archived O produto com esta referência está arquivado no Urdume: reative-o no painel.
plan_limit_reached O plano não permite mais produtos: reveja o plano ou use dados de teste.
missing_api_key / invalid_api_key (401) Chave em falta, errada ou revogada.
api_not_in_plan (403) Chave sk_live_ num plano sem API em produção: use sk_test_ ou reveja o plano.
Guia seguinte Webhooks: receber e verificar →