VITE_HUBSPOT_API_KEY=pat-na1-xxxx : cette ligne, retrouvée dans un fichier .env préfixé pour être exposée côté client, a suffi à révéler qu’une clé d’API privée HubSpot circulait en clair dans le bundle JavaScript d’un front SvelteKit, visible par quiconque ouvrait les outils de développement du navigateur sur la page de contact du site.
Le composant de formulaire appelait directement l’API de création de contact HubSpot depuis le navigateur, plutôt que de passer par un intermédiaire serveur. Cet article ne traite pas du tracking marketing ou du scoring des leads dans HubSpot : il porte uniquement sur la manière de transmettre une soumission de formulaire sans jamais exposer de clé privée.
Le problème : une clé qui n’a rien à faire dans le navigateur
Toute variable d’environnement préfixée VITE_ (ou PUBLIC_ selon la configuration de SvelteKit) est délibérément incluse dans le bundle JavaScript envoyé au navigateur, puisqu’elle est conçue pour ce cas d’usage. Une clé d’API privée HubSpot n’a jamais sa place derrière ce préfixe : quiconque inspecte le code source de la page peut l’extraire et l’utiliser pour interroger l’API HubSpot avec les mêmes permissions que le site lui-même.
La solution : un endpoint serveur comme proxy
SvelteKit propose nativement des routes serveur, exécutées uniquement côté backend, jamais envoyées au navigateur. En y déplaçant l’appel à HubSpot, la clé d’API reste entièrement côté serveur, dans une variable d’environnement standard (non préfixée), inaccessible depuis le client.

Le snippet : la route serveur
// src/routes/api/contact/+server.js
import { json } from '@sveltejs/kit';
import { HUBSPOT_API_KEY, HUBSPOT_FORM_GUID, HUBSPOT_PORTAL_ID } from '$env/static/private';
export async function POST({ request }) {
const { email, prenom, message } = await request.json();
if (!email || !prenom) {
return json({ error: 'Champs requis manquants' }, { status: 400 });
}
const response = await fetch(
`https://api.hsforms.com/submissions/v3/integration/submit/${HUBSPOT_PORTAL_ID}/${HUBSPOT_FORM_GUID}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fields: [
{ name: 'email', value: email },
{ name: 'firstname', value: prenom },
{ name: 'message', value: message },
],
}),
}
);
if (!response.ok) {
return json({ error: 'Echec de soumission' }, { status: 502 });
}
return json({ success: true });
}
Fait notable ici : l’endpoint public de soumission de formulaire HubSpot (api.hsforms.com/submissions) ne nécessite en réalité pas de clé d’API privée — il fonctionne avec l’identifiant de portail et de formulaire, tous deux publics. La clé privée HubSpot devient nécessaire uniquement si le proxy doit aussi enrichir le contact via l’API CRM classique (association à une liste, mise à jour d’une propriété personnalisée), auquel cas elle reste, comme ici, strictement côté serveur.
Le composant front, inchangé dans sa logique
// src/routes/contact/+page.svelte
<script>
let email = '';
let prenom = '';
let message = '';
let statut = 'idle';
async function envoyer() {
statut = 'envoi';
const res = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, prenom, message }),
});
statut = res.ok ? 'succes' : 'erreur';
}
</script>
Le composant n’a jamais connaissance de HubSpot directement : il appelle un endpoint interne au site, ce qui simplifie aussi un éventuel changement de fournisseur de CRM plus tard, sans toucher au composant de formulaire lui-même.
Variantes
- Limitation de débit : un compteur simple par adresse IP sur l’endpoint
/api/contactévite qu’un script automatisé ne sature l’API HubSpot via ce proxy. - Validation côté serveur renforcée : au-delà des champs requis, une vérification basique du format d’adresse e-mail avant l’appel à HubSpot évite des allers-retours inutiles pour des soumissions manifestement invalides.
- Honeypot anti-spam : un champ caché, jamais rempli par un humain, ajouté à la validation du proxy, filtre une bonne partie des soumissions automatisées sans recourir à un captcha visible.
Une règle simple à appliquer systématiquement : si une clé permet d’agir au nom du site sur un service tiers, elle ne doit jamais quitter le serveur, même pour gagner un aller-retour réseau.
En résumé
Ce proxy ne change rien à la manière dont HubSpot traite ensuite le contact créé, ni à la configuration du tracking marketing associé à ce formulaire. Il referme uniquement une faille d’exposition évitable : celle d’une clé privée qui n’avait, dès le départ, aucune raison de transiter par le navigateur.