Passer au contenu principal
Ajoutez des signatures électroniques juridiquement valables à n’importe quelle application Supabase. Utilisez les Supabase Edge Functions pour appeler l’API Firma, stockez le statut de signature dans votre base de données Postgres et recevez des mises à jour en temps réel via des webhooks.

Ce que vous pouvez créer

  • Flux de signature de contrats — déclenchez des demandes de signature lorsque les utilisateurs remplissent un formulaire ou passent commande
  • Tableaux de bord de statut des documents — suivez quels documents sont en attente, signés ou refusés
  • Signature intégrée — permettez aux utilisateurs de signer des documents sans quitter votre application
  • Flux de travail automatisés — utilisez les webhooks Firma et les déclencheurs de base de données Supabase pour lancer des actions en aval lorsque les documents sont signés

Prérequis

  • Un compte Firma avec une clé API
  • Un projet Supabase avec les Edge Functions activées
  • Le CLI Supabase installé localement

Vue d’ensemble de l’architecture

Your Frontend (React, Next.js, Flutter, etc.)


Supabase Edge Function ──► Firma API
        │                      │
        ▼                      │ (webhook)
Supabase Postgres  ◄──────────┘
  (signing_requests table)
Votre frontend appelle une Supabase Edge Function, qui appelle de manière sécurisée l’API Firma pour créer des demandes de signature. Lorsque les destinataires signent (ou refusent), Firma envoie un webhook à une autre Edge Function qui met à jour votre base de données.

Étape 1 : Stocker votre clé API Firma

Ajoutez votre clé API Firma en tant que secret Supabase afin que les Edge Functions puissent y accéder de manière sécurisée :
supabase secrets set FIRMA_API_KEY=your_api_key_here

Étape 2 : Créer une table pour suivre les demandes de signature

Exécutez ce SQL dans l’éditeur SQL de Supabase pour créer une table de suivi du statut des documents :
create table signing_requests (
  id uuid primary key default gen_random_uuid(),
  firma_request_id text unique,
  user_id uuid references auth.users(id),
  template_id text,
  status text default 'pending',
  created_at timestamptz default now(),
  updated_at timestamptz default now()
);

-- Enable Row Level Security
alter table signing_requests enable row level security;

-- Users can only see their own signing requests
create policy "Users can view own requests"
  on signing_requests for select
  using (auth.uid() = user_id);

Étape 3 : Créer une Edge Function pour envoyer les demandes de signature

Générez une nouvelle Edge Function :
supabase functions new send-signing-request
Puis remplacez le contenu de supabase/functions/send-signing-request/index.ts :
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "authorization, content-type",
};

Deno.serve(async (req) => {
  if (req.method === "OPTIONS") {
    return new Response("ok", { headers: corsHeaders });
  }

  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
  );

  const { template_id, signer_email, signer_first_name, signer_last_name } =
    await req.json();

  // Get the authenticated user
  const authHeader = req.headers.get("Authorization")!;
  const {
    data: { user },
  } = await supabase.auth.getUser(authHeader.replace("Bearer ", ""));

  if (!user) {
    return new Response(JSON.stringify({ error: "Unauthorized" }), {
      status: 401,
      headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  }

  // Create a signing request via Firma API
  const firmaResponse = await fetch(
    "https://api.firma.dev/functions/v1/signing-request-api/signing-requests",
    {
      method: "POST",
      headers: {
        Authorization: Deno.env.get("FIRMA_API_KEY"),
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        template_id,
        recipients: [
          {
            first_name: signer_first_name,
            last_name: signer_last_name,
            email: signer_email,
            designation: "Signer",
            order: 1,
          },
        ],
      }),
    }
  );

  const firmaData = await firmaResponse.json();

  if (!firmaResponse.ok) {
    return new Response(JSON.stringify({ error: firmaData }), {
      status: firmaResponse.status,
      headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  }

  // Store the signing request in your database
  const { error } = await supabase.from("signing_requests").insert({
    firma_request_id: firmaData.id,
    user_id: user.id,
    template_id,
    status: "pending",
  });

  if (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  }

  return new Response(JSON.stringify(firmaData), {
    status: 201,
    headers: { ...corsHeaders, "Content-Type": "application/json" },
  });
});
Déployez-la :
supabase functions deploy send-signing-request

Étape 4 : Gérer les webhooks Firma

Créez une autre Edge Function pour recevoir les événements webhook de Firma lorsque des documents sont signés, refusés ou mis à jour :
supabase functions new firma-webhook
Remplacez le contenu de supabase/functions/firma-webhook/index.ts :
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

Deno.serve(async (req) => {
  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
  );

  const payload = await req.json();
  const { type, data } = payload;

  // Map Firma event types to a simple status
  const statusMap: Record<string, string> = {
    "signing_request.completed": "completed",
    "signing_request.cancelled": "cancelled",
    "signing_request.expired": "expired",
    "signing_request.sent": "sent",
    "signing_request.recipient.signed": "recipient_signed",
    "signing_request.recipient.declined": "declined",
  };

  const status = statusMap[type];
  if (!status) {
    // Event type we don't need to track
    return new Response(JSON.stringify({ received: true }), { status: 200 });
  }

  // The signing request ID is nested inside data.signing_request
  const signingRequestId = data.signing_request?.id;
  if (!signingRequestId) {
    return new Response(JSON.stringify({ error: "Missing signing request ID" }), {
      status: 400,
    });
  }

  const { error } = await supabase
    .from("signing_requests")
    .update({
      status,
      updated_at: new Date().toISOString(),
    })
    .eq("firma_request_id", signingRequestId);

  if (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
    });
  }

  return new Response(JSON.stringify({ received: true }), { status: 200 });
});
Déployez-la et rendez-la publiquement accessible (les webhooks n’envoient pas d’en-têtes d’authentification) :
supabase functions deploy firma-webhook --no-verify-jwt
Enregistrez ensuite l’URL du webhook dans votre tableau de bord Firma sous Paramètres → Webhooks. L’URL de votre endpoint sera :
https://<your-project-ref>.supabase.co/functions/v1/firma-webhook
Pour une utilisation en production, vérifiez la signature du webhook à l’aide de votre secret de signature de webhook Firma. Consultez le guide des webhooks pour plus de détails sur la vérification de signature.

Étape 5 : Intégrer l’expérience de signature (facultatif)

Pour une expérience de signature intégrée à l’application, utilisez le composant signature intégrable de Firma. Une fois que vous avez le signing_request_user_id de la réponse de l’API, chargez-le dans une iframe :
<iframe
  src="https://app.firma.dev/signing/{signing_request_user_id}"
  style="width:100%;height:900px;border:0;"
  allow="camera;microphone;clipboard-write"
  title="Document Signing"
></iframe>
Consultez le guide de signature intégrée pour des instructions de configuration complètes, y compris les bonnes pratiques de sécurité.

Étape 6 : Interroger le statut de signature depuis votre frontend

Avec la sécurité au niveau des lignes (Row Level Security) en place, votre frontend peut interroger directement le statut de signature :
const { data: requests } = await supabase
  .from("signing_requests")
  .select("*")
  .order("created_at", { ascending: false });

Étapes suivantes