The Stood CRM Web Form allows you to embed a contact/lead capture form on any website. Form submissions automatically create:
Account (company or based on last name if no company)
Contact (person submitting)
Deal (opportunity with default stage s0)
All entities are properly linked together, with smart duplicate handling for existing contacts.
You need two pieces of information from your Stood CRM team:
Team ID: Your unique team identifier
Team Key: Your API authentication key
Use the complete HTML example below. Update the CONFIG section with your credentials:
const CONFIG = {
teamId: 'YOUR_TEAM_ID',
teamKey: 'YOUR_TEAM_KEY',
endpoint: 'https://BASE_URL.cloudfunctions.net/webFormSubmit'
};Save the HTML file and host it on any web server, or embed it in your existing website.
Use dot notation to specify which CRM object the field belongs to:
Contact Fields:
contact.firstName - Contact first name
contact.lastName - Contact last name
contact.email - Contact email
contact.phone - Contact phone
Account Fields:
account.name - Company name (optional - defaults to contact's last name)
Deal Fields:
deal.description - Deal notes/description
deal.partnerKey - Partner/referral tracking code
deal.partnerEmail - Partner/referral email
Tracking:
sourceName - Lead source (e.g., "Website", "Partner Portal", "LinkedIn")
Any additional fields are automatically stored as custom fields:
account.customFields.category
contact.customFields.industry
deal.customFields.budget
Just use the format: objectType.fieldName in your form inputs.
You can pre-fill form fields via URL parameters:
https://yoursite.com/contact?partner_key=ABC123&partner_email=partner@example.com&source=LinkedIn
The form automatically:
Fills in matching fields
Sets them to read-only if pre-filled
Shows/hides sections based on available data
When a form is submitted:
Validation: Team key is verified and rate limiting is applied
Account Creation:
Finds existing account by name, or creates new one
If no company name provided, uses contact's last name
Contact Creation:
Checks if contact exists by email/phone
Creates new contact or updates existing one
If contact exists with different account, preserves their original account link
Deal Creation:
Always creates a new deal for each submission
Default deal name: AccountName - sourceName (or LastName - sourceName)
Default stage: s0
Default closing date: 3 months from today
Automatically links to account and adds contact to relatedContacts
If contact already existed, adds note to deal description
Team key authentication prevents unauthorized submissions
In-memory rate limiting (10 requests per minute per IP)
Proper error handling and validation
Cloud Function runs with admin privileges (bypasses Firestore rules safely)
Below is a fully functional form ready to use:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stood CRM - Contact Form</title>
<style>
body {
font-family: system-ui, system, sans-serif;
max-width: 500px;
margin: 50px auto;
padding: 20px;
background: #f5f5f5;
}
form {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h2 {
margin-top: 0;
}
input, textarea {
width: 100%;
padding: 10px;
margin: 8px 0;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
textarea {
min-height: 80px;
font-family: inherit;
resize: vertical;
}
input:read-only {
background: #f0f0f0;
color: #666;
cursor: not-allowed;
}
.partner-section {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #e0e0e0;
}
.section-title {
font-size: 14px;
font-weight: 600;
color: #666;
margin-bottom: 10px;
}
button {
width: 100%;
padding: 12px;
background: #4f46e5;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
margin-top: 10px;
}
button:hover {
background: #4338ca;
}
button:disabled {
background: #9ca3af;
cursor: not-allowed;
}
.message {
padding: 12px;
margin: 10px 0;
border-radius: 4px;
}
.success {
background: #d1fae5;
color: #065f46;
}
.error {
background: #fee2e2;
color: #991b1b;
}
.dashboard-section {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
text-align: center;
margin-top: 20px;
}
.dashboard-btn {
display: inline-block;
padding: 12px 24px;
background: #6b7280;
color: white;
text-decoration: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background 0.2s;
}
.dashboard-btn:hover {
background: #4b5563;
}
</style>
</head>
<body>
<form id="contactForm">
<h2>Contact Us</h2>
<div id="message"></div>
<input type="text" name="contact.firstName" placeholder="First Name *" required>
<input type="text" name="contact.lastName" placeholder="Last Name *" required>
<input type="email" name="contact.email" placeholder="Email *" required>
<input type="tel" name="contact.phone" placeholder="Phone">
<input type="text" name="account.name" placeholder="Company">
<textarea name="deal.description" placeholder="Message (optional)"></textarea>
<!-- Partner/Referral Section (hidden by default, shown when URL has partner params) -->
<div class="partner-section" id="partnerSection" style="display: none;">
<div class="section-title">Partner Information</div>
<input type="text" name="deal.partnerKey" id="partnerKey" placeholder="Partner Key">
<input type="email" name="deal.partnerEmail" id="partnerEmail" placeholder="Partner Email (optional)">
</div>
<!-- Hidden tracking fields -->
<input type="hidden" name="sourceName" value="Website">
<button type="submit" id="submitBtn">Submit</button>
</form>
<div class="dashboard-section">
<a href="" class="dashboard-btn">Access Dashboard</a>
</div>
<script>
// ==========================================
// CONFIGURATION - UPDATE THESE VALUES
// ==========================================
const CONFIG = {
teamId: 'YOUR_TEAM_ID',
teamKey: 'YOUR_TEAM_KEY',
endpoint: 'https://BASE_URL.cloudfunctions.net/webFormSubmit'
};
// ==========================================
// FORM HANDLING CODE
// ==========================================
const form = document.getElementById('contactForm');
const submitBtn = document.getElementById('submitBtn');
const messageDiv = document.getElementById('message');
form.addEventListener('submit', async (e) => {
e.preventDefault();
// Disable form during submission
submitBtn.disabled = true;
submitBtn.textContent = 'Submitting...';
messageDiv.innerHTML = '';
// Collect form data
const formData = {};
const inputs = form.querySelectorAll('input[name], textarea[name]');
inputs.forEach(input => {
if (input.value.trim()) {
formData[input.name] = input.value.trim();
}
});
try {
// Submit to Stood CRM
const response = await fetch(CONFIG.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
teamId: CONFIG.teamId,
teamKey: CONFIG.teamKey,
formData: formData
})
});
const result = await response.json();
if (result.success) {
// Success!
messageDiv.innerHTML = '<div class="message success">✓ Thank you! We\'ll be in touch soon.</div>';
form.reset();
// Optional: Redirect after success
// setTimeout(() => window.location.href = '/thank-you', 2000);
} else {
// Error from server
messageDiv.innerHTML = `<div class="message error">✗ ${result.error || 'Submission failed. Please try again.'}</div>`;
}
} catch (error) {
// Network or other error
console.error('Submission error:', error);
messageDiv.innerHTML = '<div class="message error">✗ Network error. Please check your connection and try again.</div>';
} finally {
// Re-enable form
submitBtn.disabled = false;
submitBtn.textContent = 'Submit';
}
});
// Optional: Auto-fill partner info from URL
// Example: yoursite.com/contact?partner_key=ABC123&partner_email=partner@example.com&source=LinkedIn
const params = new URLSearchParams(window.location.search);
const partnerSection = document.getElementById('partnerSection');
const partnerKeyInput = document.getElementById('partnerKey');
const partnerEmailInput = document.getElementById('partnerEmail');
let hasPartnerInfo = false;
if (params.has('partner_key')) {
partnerKeyInput.value = params.get('partner_key');
partnerKeyInput.readOnly = true;
hasPartnerInfo = true;
}
if (params.has('partner_email')) {
partnerEmailInput.value = params.get('partner_email');
partnerEmailInput.readOnly = true;
hasPartnerInfo = true;
}
if (params.has('source')) {
document.querySelector('[name="sourceName"]').value = params.get('source');
}
// Show partner section if any partner info is present
if (hasPartnerInfo) {
partnerSection.style.display = 'block';
}
</script>
</body>
</html>Styling: Modify the <style> section to match your brand
Fields: Add/remove fields as needed using the objectType.fieldName pattern
Success Message: Customize the success message or add a redirect
Validation: Add client-side validation for better UX
Analytics: Add tracking code to monitor form submissions
You can test the endpoint using curl:
curl -X POST https://BASE_URL.cloudfunctions.net/webFormSubmit \
-H "Content-Type: application/json" \
-d '{
"teamId": "YOUR_TEAM_ID",
"teamKey": "YOUR_TEAM_KEY",
"formData": {
"contact.firstName": "John",
"contact.lastName": "Doe",
"contact.email": "john.doe@example.com",
"contact.phone": "+1234567890",
"account.name": "Acme Corp",
"deal.description": "Interested in your product",
"sourceName": "Website"
}
}'