Factuarea APIDevelopers

Webhooks

Notificacions POST signades amb HMAC SHA256. Verificació, reintents exponencials i rotació de secret.

Els webhooks notifiquen al teu servidor quan passa un esdeveniment a Factuarea (factura pagada, pressupost acceptat, client creat, etc.) sense que hagis de fer polling. Cada esdeveniment es lliura a la teva URL mitjançant un POST HTTPS signat.

Aquesta guia cobreix els endpoints d'un NIF, sota /v1/companies/{company}/webhook-endpoints. Per rebre els esdeveniments d'un compte sencer en un sol endpoint, consulta Webhooks de compte.

No es lliuren en mode de prova. Els esdeveniments generats amb una clau fact_test_ (sandbox) es registren però mai es lliuren als teus endpoints externs, test-event inclòs. Una clau de proves no pot cridar els endpoints de webhook d'un NIF, ping inclòs, perquè no pot portar webhooks:write. POST /v1/companies/{company}/webhook-endpoints/{id}/ping, cridat amb una clau live, només comprova que la teva URL respon: no porta signatura. Consulta Mode de prova i sandbox. Per exercitar la verificació de signatura i el teu handler real end-to-end, envia en el seu lloc un test-event amb una clau live.

Crear un endpoint

curl -X POST https://api.factuarea.com/v1/companies/{company}/webhook-endpoints \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://app.mycompany.com/factuarea/webhook",
    "description": "Sync with internal CRM",
    "enabled_events": [
      "invoice.created",
      "invoice.paid",
      "quote.approved"
    ]
  }'

Resposta, abreujada (el secret es retorna només una vegada):

{
  "data": {
    "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b",
    "object": "webhook_endpoint",
    "url": "https://app.mycompany.com/factuarea/webhook",
    "description": "Sync with internal CRM",
    "enabled_events": ["invoice.created", "invoice.paid", "quote.approved"],
    "status": "active",
    "api_version": "2026-09-23",
    "created_at": "2026-09-28T10:23:18Z",
    "secret": "whsec_01HKQS5N8VR7QXJ9K3T6BWPMZA9876543210ABCDEF"
  }
}

enabled_events enumera noms d'esdeveniment del catàleg, un per un, com a mínim un. Un comodí com * o invoice.* no és un nom vàlid i respon 422, així que anomena cada esdeveniment que vulguis. El catàleg complet és a Esdeveniments.

Signatura HMAC SHA256

Cada lliurament inclou aquestes headers:

Factuarea-Signature: t=1747314060,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Factuarea-Event-Id: 01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0d
Factuarea-Event-Type: invoice.paid
Factuarea-Delivery-Id: 01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0c
Idempotency-Key: 01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0d
  • t — timestamp UNIX del lliurament (segons).
  • v1 — HMAC SHA256 de la cadena {t}.{body} amb el secret de l'endpoint, en hex.
  • Idempotency-Key — el header estàndard de la indústria, amb el mateix valor que Factuarea-Event-Id (l'UUID v7 estable de l'esdeveniment). Fes-lo servir per deduplicar lliuraments repetits amb el header que el teu stack potser ja entén. Consulta Idempotència per la teva banda.

Fas servir un SDK oficial? Salta't l'HMAC manual de sota — tant el SDK de TypeScript com el de PHP inclouen un verificador de webhooks que fa per tu la comparació en temps constant, la tolerància del timestamp i la gestió del període de gràcia de rotació. Consulta Verificar webhooks amb el SDK. La recepta manual de sota és per a qualsevol altre llenguatge.

Per validar:

  1. Extreu t i v1 de Factuarea-Signature.
  2. Calcula signed_payload = t + "." + raw_body (bytes crus del body, sense reformatar el JSON).
  3. Calcula expected = hmac_sha256(secret, signed_payload) en hex.
  4. Compara v1 == expected fent servir comparació en temps constant.
  5. Comprova que |now - t| <= 300 (tolerància de ±5 minuts contra atacs de replay).

Durant el període de gràcia d'una rotació de secret la header porta dos valors v1 — un per cada secret actiu (t=...,v1=<current>,v1=<previous>). Accepta la petició si coincideix qualsevol dels v1. Consulta Rotació de secret.

function verifyFactuareaSignature(
    string $payload,
    string $signatureHeader,
    string $secret,
    int $toleranceSeconds = 300,
): bool {
    $timestamp = null;
    $signatures = [];
    foreach (explode(',', $signatureHeader) as $kv) {
        [$k, $v] = explode('=', $kv, 2);
        if ($k === 't') {
            $timestamp = (int) $v;
        } elseif ($k === 'v1') {
            $signatures[] = $v;
        }
    }
    if ($timestamp === null || $signatures === []) {
        return false;
    }
    if (abs(time() - $timestamp) > $toleranceSeconds) {
        return false;
    }
    $expected = hash_hmac('sha256', $timestamp.'.'.$payload, $secret);

    foreach ($signatures as $candidate) {
        if (hash_equals($expected, $candidate)) {
            return true;
        }
    }

    return false;
}

// In the webhook handler:
$payload = file_get_contents('php://input');
$header = $_SERVER['HTTP_FACTUAREA_SIGNATURE'] ?? '';
$secret = getenv('FACTUAREA_WEBHOOK_SECRET');

if (! verifyFactuareaSignature($payload, $header, $secret)) {
    http_response_code(401);
    exit;
}

$event = json_decode($payload, true);
handleEvent($event);

http_response_code(200);
const crypto = require('crypto');

function verifySignature(payload, header, secret, tolerance = 300) {
  let timestamp = null;
  const signatures = [];
  for (const kv of header.split(',')) {
    const [k, v] = kv.split('=');
    if (k === 't') timestamp = Number(v);
    else if (k === 'v1') signatures.push(v);
  }
  if (timestamp === null || signatures.length === 0) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > tolerance) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${payload}`)
    .digest('hex');

  return signatures.some((candidate) =>
    crypto.timingSafeEqual(
      Buffer.from(expected, 'hex'),
      Buffer.from(candidate, 'hex')
    )
  );
}

// Express:
app.post('/factuarea/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const payload = req.body.toString('utf8');
  if (!verifySignature(payload, req.header('Factuarea-Signature'), process.env.WHSEC)) {
    return res.status(401).end();
  }
  const event = JSON.parse(payload);
  handleEvent(event);
  res.status(200).end();
});
import hmac, hashlib, time
from flask import request, abort

def verify(payload: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    timestamp = None
    signatures = []
    for kv in header.split(','):
        k, v = kv.split('=', 1)
        if k == 't':
            timestamp = int(v)
        elif k == 'v1':
            signatures.append(v)
    if timestamp is None or not signatures:
        return False
    if abs(time.time() - timestamp) > tolerance:
        return False
    signed = f"{timestamp}.".encode() + payload
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, candidate) for candidate in signatures)

@app.post('/factuarea/webhook')
def webhook():
    if not verify(request.get_data(), request.headers.get('Factuarea-Signature', ''), WHSEC):
        abort(401)
    event = request.get_json()
    handle_event(event)
    return '', 200

Verificar amb el SDK oficial

Els SDK de TypeScript i PHP embolcallen els cinc passos de dalt — comparació en temps constant, tolerància de ±5 minuts i període de gràcia de rotació — en una sola crida. Passa el body cru de la petició, la header Factuarea-Signature i el secret de l'endpoint:

import { Factuarea, WebhookSignatureError, SIGNATURE_HEADER } from "@factuarea/sdk";

const factuarea = new Factuarea({ apiKey: process.env.FACTUAREA_API_KEY! });

// Express, with express.raw({ type: "application/json" }) on the route:
app.post("/webhooks/factuarea", (req, res) => {
  try {
    const event = factuarea.webhooks.verify(
      req.body.toString("utf8"),
      req.headers[SIGNATURE_HEADER.toLowerCase()] as string,
      process.env.FACTUAREA_WEBHOOK_SECRET!,
    );
    if (event.type === "invoice.paid") { /* … */ }
    res.sendStatus(200);
  } catch (e) {
    if (e instanceof WebhookSignatureError) return res.sendStatus(400);
    throw e;
  }
});

Una tolerància personalitzada (en segons) és el quart argument opcional: factuarea.webhooks.verify(body, header, secret, { toleranceSeconds: 600 }).

use Factuarea\Sdk\Custom\Webhooks\WebhookVerifier;
use Factuarea\Sdk\Custom\Webhooks\WebhookSignatureException;

$verifier = new WebhookVerifier();
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_FACTUAREA_SIGNATURE'] ?? '';

try {
    $event = $verifier->verify($rawBody, $signature, getenv('FACTUAREA_WEBHOOK_SECRET'));
    // $event is the decoded, authenticated payload
    if (($event['type'] ?? null) === 'invoice.paid') { /* … */ }
    http_response_code(200);
} catch (WebhookSignatureException $e) {
    http_response_code(400);
}

Tots dos verificadors accepten les dues signatures v1 durant el període de gràcia d'una rotació de secret (consulta Rotació de secret), de manera que una rotació mai descarta un lliurament.

Reintents

Si el teu endpoint respon amb un 5xx, un 408, 425 o 429, o no respon en 10 segons, Factuarea reintenta amb back-off exponencial. Qualsevol altre 4xx, i una URL que Factuarea es nega a cridar per motius de seguretat, acaben el lliurament com a failed_permanently sense reintent.

IntentEspera després de l'anterior
1immediat
21 minut
35 minuts
430 minuts
52 hores
612 hores
71 dia
83 dies

Cada espera varia fins a un ±10 %. Després de l'intent final el lliurament passa a failed_permanently i deixa de reintentar-se. Els lliuraments romanen visibles a GET /v1/companies/{company}/webhook-endpoints/{id}/deliveries durant 90 dies des que es van crear, i els esdeveniments que hi ha al darrere es conserven 30 dies. Pots reintentar manualment un lliurament failed o failed_permanently mitjançant POST /v1/companies/{company}/webhook-endpoints/{id}/deliveries/{delivery}/replay o des del dashboard, mentre el seu esdeveniment continuï emmagatzemat: passats 30 dies el replay respon 422 business_rule_violation amb el subcode replay_event_expired. Un lliurament en qualsevol altre estat respon el mateix 422 amb el subcode replay_delivery_not_retryable.

Body del lliurament

El body de cada lliurament és el sobre signat de l'esdeveniment:

{
  "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0d",
  "type": "invoice.paid",
  "api_version": "2026-09-23",
  "created": 1774002600,
  "livemode": true,
  "test": false,
  "correlation_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a99",
  "data": {
    "type": "invoice.paid",
    "object": {
      "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a42",
      "object": "invoice",
      "number": "FAC-2026-00042",
      "status": "paid",
      "total": "1210.00",
      "currency": "EUR"
    },
    "amount": "1210.00",
    "payment_date": "2026-03-20",
    "payment_method": "bank_transfer",
    "payment_reference": "TRF-2026-0042"
  }
}

La factura de data.object s'abreuja aquí a sis dels seus camps. Aquests són els camps del sobre:

  • id — l'identificador de l'esdeveniment (UUID v7). És també el header Factuarea-Event-Id.
  • type — el nom de l'esdeveniment, com al catàleg.
  • api_version — la versió de payload per data en què està construït data: la que té fixada el teu endpoint, o l'actual. Sempre hi és (null només per a esdeveniments antics emesos abans de segellar les versions).
  • created — quan es va crear l'esdeveniment, com a timestamp Unix en segons.
  • livemode i test — vegeu a sota.
  • correlation_id — un UUID v7 que enllaça els esdeveniments provocats per una mateixa operació, o null quan no n'hi ha cap. La clau sempre és present.
  • data — { "type", "object" }. object és el snapshot complet del recurs afectat tal com era quan va passar l'esdeveniment, idèntic al que retornava un GET d'aquell recurs en aquell moment, i està congelat: els canvis posteriors del recurs no reescriuen els esdeveniments passats. Les claus pròpies de l'esdeveniment van al costat d'object, com amount i payment_date a invoice.paid.

El body no porta object: "event" ni aggregate_id: són de l'objecte d'esdeveniment que llegeixes amb GET /v1/companies/{company}/events, que porta els mateixos id, type, created i data.

El camp test és sempre present: true només per a un lliurament de prova enviat amb test-event (vegeu a sota), false per a esdeveniments reals. És ortogonal a livemode: test indica si aquest lliurament és de prova, mentre que livemode reflecteix l'entorn de la key (producció vs sandbox). Un lliurament de prova es pot emetre en qualsevol dels dos, així que livemode: true, test: true és vàlid.

Versió del payload

Cada endpoint té un api_version que fixa la forma dels snapshots de recurs que rep a data.object. Les versions de payload admeses són 2026-05-22, 2026-10-01 i 2026-10-05 (la més recent), i l'api_version de cada sobre entregat és la versió d'aquella entrega.

api_version de l'endpointFactures a data.object
2026-10-05El vocabulari de 2026-10-01, amb tots els imports com a cadena decimal i la data de cobrament com a paid_recorded_at i paid_date (paid_at i paid_on desapareixen). La reben els endpoints creats amb la versió REST 2026-10-01 o moguts aquí amb api_version.
2026-10-01status: "issued" per a una factura emesa, amb issued_at, sent_at (primer lliurament), sent_via i is_sent, sobre els imports numèrics i paid_at/paid_on de 2026-05-22.
2026-05-22 (tot endpoint que existia abans de 2026-10-01, i els creats sense api_version amb una versió REST anterior)El vocabulari anterior: status: "sent", sent_at igual a l'instant d'emissió, sense issued_at, is_sent ni sent_via.

Un endpoint creat sense api_version queda fixat en crear-se. Sense capçalera Factuarea-Version ni versió fixada a la clau, o des de l'aplicació, es queda a 2026-05-22; si la petició que el crea fa servir la versió REST 2026-10-01, pren la versió de payload més recent (2026-10-05). Per començar amb el vocabulari nou, crea'l amb Factuarea-Version: 2026-10-01 o amb "api_version": "2026-10-01".

L'esdeveniment desat no es reescriu mai: la projecció es fa en entregar. Passa un endpoint al vocabulari nou amb PATCH /v1/companies/{company}/webhook-endpoints/{webhook_endpoint} i "api_version": "2026-10-01" quan el teu receptor accepti issued. Consulta Versionat.

Resposta esperada

  • Qualsevol status 2xx → lliurament delivered, sigui quin sigui el body.
  • Un 5xx, un 408, 425 o 429, o cap resposta en 10 segons → lliurament failed, es programa el següent reintent.
  • Qualsevol altre 4xx → lliurament failed_permanently, sense reintent.
  • El body és irrellevant. No el processem — el log de lliuraments desa response_status, duration_ms i una còpia truncada del body (response_body_truncated) perquè puguis depurar.

Replay des del dashboard

Developers > Webhooks > Deliveries et permet reintentar un lliurament a mà, igual que el replay de l'API: funciona amb els lliuraments failed i failed_permanently, mentre el seu esdeveniment continuï emmagatzemat, i encua un lliurament nou del mateix esdeveniment amb el mateix body. El lliurament original conserva el seu historial.

Lliuraments de prova

Dos endpoints et permeten exercitar el teu receptor sense esperar un esdeveniment real — resolen problemes diferents:

  • POST /v1/companies/{company}/webhook-endpoints/{id}/ping envia una petició sintètica { "type": "webhook.ping" } a la teva URL i retorna el resultat a la seva pròpia resposta (success, http_status, response_body, error_message i duration_ms). No va signada, mai entra al log d'esdeveniments, no es desa com a lliurament i no és un tipus d'esdeveniment real. Fes-lo servir per confirmar que la teva URL és accessible. Necessita una clau live: una clau fact_test_ no el pot cridar, perquè no pot portar webhooks:write.
  • POST /v1/companies/{company}/webhook-endpoints/{id}/test-event dispara un lliurament de prova d'un tipus d'esdeveniment real del catàleg, marcat amb "test": true a l'envelope. Registra un Event real (visible a GET /v1/companies/{company}/events) i encua un WebhookDelivery signat i reintentat exactament igual que un lliurament de producció — així valides el teu handler real end-to-end. El seu data.object és un marcador de posició amb un id inventat, no un snapshot de les teves dades. Des del sandbox es registra i mai no es lliura.
curl -X POST https://api.factuarea.com/v1/companies/{company}/webhook-endpoints/{id}/test-event \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "invoice.paid" }'

type és opcional: omet-lo per fer servir el primer esdeveniment subscrit de l'endpoint. Si l'indiques, ha de ser un dels enabled_events de l'endpoint (si no, 422 amb el subcode event_not_subscribed). El lliurament arriba només a aquest endpoint, mai als altres endpoints subscrits al mateix tipus.

Rotació de secret (dual-signing)

curl -X POST https://api.factuarea.com/v1/companies/{company}/webhook-endpoints/{id}/rotate-secret \
  -H "Authorization: Bearer $FACTUAREA_API_KEY"

Retorna el nou secret. Durant 24 hores (l'instant previous_secret_valid_until de la resposta) tots dos secrets són vàlids: cada lliurament es signa dues vegades a la mateixa header Factuarea-Signature — un v1 per secret (t=...,v1=<current>,v1=<previous>). Després de la finestra, el secret antic queda invalidat.

Et permet desplegar el nou secret amb zero temps d'inactivitat:

  1. Crida /rotate-secret → obtén el nou secret.
  2. Desplega el nou secret al teu entorn.
  3. El teu handler accepta qualsevol dels v1 durant el període de gràcia (els helpers de verificació de dalt ja recorren tots els v1).
  4. Després de la finestra, només el nou secret està en ús.

Idempotència de la teva banda

Cada esdeveniment inclou un camp id (UUID v7, únic) que no canvia mai: els reintents automàtics d'un lliurament, un replay manual i les còpies que reben altres endpoints porten tots el mateix id — i el header Idempotency-Key porta aquest mateix valor exacte, així que pots deduplicar des del header sense parsejar el body. L'id és també el mateix als dos eixos: un endpoint d'empresa i un endpoint de compte que admeten el mateix esdeveniment d'un NIF reben el mateix id als seus dos sobres. Desa els ids que hagis processat i respon 2xx sense actuar si ja n'has processat algun.

Deduplicar lliuraments de webhooks té la recepta completa: una clau única sobre l'id, la signatura verificada primer, quant de temps cal conservar els ids i què torna a enviar el mateix id.

Llista d'accés d'IP (opcional)

Si el teu endpoint corre darrere d'un firewall que filtra per IP, pots restringir les IPs d'origen mitjançant ip_allowlist en crear l'endpoint. Factuarea lliura des d'un pool d'IPs estables documentades al dashboard.

Valida la signatura HMAC, no la IP — les IPs poden canviar amb 30 dies d'avís, les signatures no.

Esdeveniments disponibles

El catàleg complet el retorna GET /v1/event-catalog i està documentat a Esdeveniments. Exemples clau:

  • invoice.created, invoice.updated, invoice.issued, invoice.marked_sent, invoice.unsent, invoice.paid, invoice.annulled
  • quote.created, quote.approved, quote.rejected, quote.converted
  • proforma.accepted, proforma.converted_to_invoice
  • delivery_note.signed
  • facturae.face_submitted, facturae.face_status_changed, facturae.face_cancellation_requested
  • client.created, client.updated
  • task.created, task.status_changed, task.completed, task.assigned, task_comment.created, task_time_entry.invoiced, project.created

invoice.sent és un àlies deprecat d'invoice.issued: s'emet en el mateix instant i amb el mateix data.object, de manera que un endpoint subscrit a tots dos rep dues entregues. Subscriu els endpoints nous a invoice.issued. Una factura s'emet quan rep el número definitiu; passa a enviada (invoice.marked_sent) quan el client la rep. Consulta Treballar amb factures.

Esdeveniments de tasques i projectes

Els 21 esdeveniments de tasques, comentaris, temps imputat i projectes s'entreguen com qualsevol altre, i el seu data.object porta el recurs complet. task.completed és un subconjunt de task.status_changed: completar una tasca n'entrega els dos, així que un endpoint subscrit a tots dos rep dues entregues. Consulta Esdeveniments per veure la llista completa i les claus extra de cadascun.

Per escoltar un sol projecte, filtra per l'id de projecte que porta cada esdeveniment de tasca, comentari i temps: a data.object.project_id, o a data.project_id per als esborrats i els comentaris. En els esdeveniments project.* el projecte és data.object.id.

function projectIdOf(event) {
  if (event.type.startsWith("project.")) return event.data.object.id;
  return event.data.project_id ?? event.data.object.project_id;
}

const isTaskEvent = /^(task|task_comment|task_time_entry|project)\./.test(event.type);
if (isTaskEvent && projectIdOf(event) === WATCHED_PROJECT_ID) {
  await enqueue({ eventId: event.id, type: event.type, object: event.data.object });
}

Com amb qualsevol altre esdeveniment, deduplica per l'id de l'esdeveniment.

Esdeveniments de comandes

Subscriu-te a order.invoiced i order.refunded per rebre snapshots complets de la factura o rectificativa a data.object. Tots dos inclouen data.store.uuid, data.store.provider i data.order.external_id; les devolucions afegeixen data.refund.scope i data.refund.reason_code. Fes servir l’id de l’esdeveniment per deduplicar lliuraments i correlation_id per relacionar operacions. Consulta el processament de botigues i el trigger de n8n.

if (event.type === "order.invoiced" || event.type === "order.refunded") {
  const invoice = event.data.object;
  const storeId = event.data.store.uuid;
  const orderId = event.data.order.external_id;
  await saveOrderDocument({ eventId: event.id, storeId, orderId, invoice });
}

Webhooks de compte

Tot l'anterior parla dels endpoints d'un sol NIF. Conserven el seu contracte, i continuen sent l'eina adequada quan integres un únic NIF.

Un endpoint de webhook de compte és un recurs diferent, sota /v1/accounts/{account}/webhooks. Un sol endpoint rep els esdeveniments de cada NIF del teu compte, els dels comptes que gestiones i els esdeveniments account.* del mateix compte, amb el seu propi secret de signatura, el seu propi topall per tier del pla i el seu propi historial de lliuraments. Signa, reintenta i rota el seu secret com un endpoint d'empresa, així que el codi de verificació d'aquesta pàgina li serveix sense canvis. Webhooks de compte explica què rep i qui el pot gestionar, i la seva taula d'equivalències aparella cada operació amb la seva bessona d'empresa, per exemple replay amb retry, rotate-secret amb secret i test-event amb test.

Els esdeveniments account.* no són al catàleg d'esdeveniments d'empresa: un endpoint d'empresa no s'hi pot subscriure i respon 422. Un esdeveniment d'un NIF pot arribar alhora a un endpoint d'empresa i a un de compte, i el seu id és el mateix als dos sobres: consulta Idempotència de la teva banda.

Endpoints d'empresa sobre un NIF assolit per una relació

Ningú no crea ni gestiona endpoints de webhook d'empresa sobre un NIF que el teu compte assoleix per una relació amb un altre compte, sigui perquè un altre compte te l'ha cedit o perquè pertany a un compte que gestiones. Les onze operacions de …/webhook-endpoints responen 403 forbidden_action abans de llegir o canviar res, amb qualsevol nivell d'accés, i les tools MCP d'aquestes operacions responen el mateix. Sobre els teus NIF propis conserven el seu contracte.

Els registres de peticions d'aquest NIF i el seu log d'esdeveniments, GET /v1/companies/{company}/events, estan tancats igual: consulta Llistar esdeveniments via API. Per seguir els NIF dels comptes que gestiones, fes servir un endpoint de compte amb el filtre managed o all. Webhooks de compte en té el detall.

En aquesta pàgina

Et donem un cop de mà?Contactar amb suport