Contact & Subscribe Forms
Capture leads and mailing-list signups from your marketing site with the ResytechApi CRM intake controllers, including Google reCAPTCHA v3 setup.
The ResytechApi client exposes two public intake endpoints for your marketing site:
api.subscribeForm.subscribe(...)— email-only mailing-list signup. Creates a Contact withobtain_method = subscribe_formandcontact_consent = true.api.contactForm.submit(...)— full contact / lead-capture form. Creates a Contact and sends a notification email to the operator's support address.
Both endpoints are public (no booking session required) and both are protected on the server with Google reCAPTCHA v3. You need to configure a reCAPTCHA key pair before either form will accept submissions in production.
Prerequisites: Google reCAPTCHA v3
1. Generate keys in the Google reCAPTCHA admin
- Sign in to the Google reCAPTCHA admin console.
- Pick a Label (e.g. My Marketing Site).
- Choose reCAPTCHA v3.
- Add every domain that will embed your form, without
https://and without paths. For example:yourbusiness.comwww.yourbusiness.comstaging.yourbusiness.com
- Accept the terms and click Submit.
Google returns two values:
| Key | Where it's used | Sensitivity |
|---|---|---|
| Site key | Embedded in your page HTML and passed to grecaptcha.execute() in the browser | Public — safe to commit |
| Secret key | Pasted into the Resytech dashboard so the server can verify tokens | Secret — never expose to the browser |
2. Store the secret key in the Resytech dashboard
In the Resytech dashboard, navigate to CRM > Settings and paste the secret key into the reCAPTCHA Secret field, then save.
- The page shows Configured / Not configured rather than the value itself — the secret is never returned by the API once stored.
- Use the Clear stored secret action to remove it (e.g. if you're rotating keys).
- A location with no secret configured will reject every contact / subscribe submission with
"Resytech Location Not Setup".
One reCAPTCHA key pair per Resytech Location. If you run multiple locations and want them to share a key, paste the same secret into each location's settings.
3. Load reCAPTCHA on your page
Add the reCAPTCHA loader script and your site key to the page that hosts the form:
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>Then, when the user submits the form, obtain a fresh token with grecaptcha.execute() and pass it to the API call. Tokens are single-use and expire after ~2 minutes — always generate one at submit time, never reuse one.
const recaptchaSiteKey = 'YOUR_SITE_KEY';
async function getRecaptchaToken(action) {
return new Promise((resolve, reject) => {
grecaptcha.ready(() => {
grecaptcha.execute(recaptchaSiteKey, { action }).then(resolve).catch(reject);
});
});
}The action string is a label that Google uses for analytics and risk scoring. Pick a short, human-readable name per form — e.g. subscribe, contact.
SubscribeForm Controller
api.subscribeForm.subscribe(request) adds an email address to the operator's CRM as a consented contact.
const token = await getRecaptchaToken('subscribe');
const result = await api.subscribeForm.subscribe({
email: 'jane@example.com',
token,
location: 'location-uuid'
});
if (result.success) {
console.log('Subscribed!');
} else {
console.error(result.message);
}SubscribeFormRequest
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email address to subscribe |
token | string | Yes | Single-use reCAPTCHA v3 token obtained via grecaptcha.execute() |
location | string | Yes | Location UUID (the operator/venue receiving the signup) |
SubscribeFormResponse
Extends ApiResponseBase — see ResytechApi Overview for the full shape. No additional fields.
ContactForm Controller
api.contactForm.submit(request) records a full contact-form / lead-capture submission. The server creates a Contact record and emails the operator's support address with the submitted details.
const token = await getRecaptchaToken('contact');
const result = await api.contactForm.submit({
firstName: 'Jane',
lastName: 'Doe',
email: 'jane@example.com',
phone: '555-123-4567',
message: 'Hi, I have a question about your kayak tours.',
token,
location: 'location-uuid'
});
if (result.success) {
console.log('Inquiry sent');
} else {
console.error(result.message);
}ContactFormRequest
All fields are required by the server.
| Field | Type | Required | Description |
|---|---|---|---|
firstName | string | Yes | Submitter's first name |
lastName | string | Yes | Submitter's last name |
email | string | Yes | Submitter's email address |
phone | string | Yes | Submitter's phone number |
message | string | Yes | Free-text message body |
token | string | Yes | Single-use reCAPTCHA v3 token |
location | string | Yes | Location UUID |
ContactFormResponse
Extends ApiResponseBase. No additional fields.
Full Example: Subscribe form on a marketing page
<!doctype html>
<html>
<head>
<script src="https://js.resytech.com/latest/resytech.js"></script>
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>
</head>
<body>
<form id="subscribe-form">
<input type="email" name="email" required />
<button type="submit">Subscribe</button>
<p id="status"></p>
</form>
<script>
const api = new ResytechApi();
const recaptchaSiteKey = 'YOUR_SITE_KEY';
const locationUuid = 'YOUR_LOCATION_UUID';
async function getRecaptchaToken(action) {
return new Promise((resolve) => {
grecaptcha.ready(() => {
grecaptcha.execute(recaptchaSiteKey, { action }).then(resolve);
});
});
}
document.getElementById('subscribe-form').addEventListener('submit', async (e) => {
e.preventDefault();
const status = document.getElementById('status');
const email = e.target.email.value;
status.textContent = 'Subscribing...';
const token = await getRecaptchaToken('subscribe');
const result = await api.subscribeForm.subscribe({
email,
token,
location: locationUuid
});
status.textContent = result.success
? 'Thanks! You\'re subscribed.'
: `Sorry — ${result.message}`;
});
</script>
</body>
</html>The same pattern applies to api.contactForm.submit(...) — swap the form fields, the action label (contact), and the API call.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Response says "Resytech Location Not Setup" | The Location's reCAPTCHA secret hasn't been saved in the dashboard yet. See Step 2 above. |
| Response says "Captcha verification failed" (or 403 in release builds) | The site key in your page doesn't match the secret saved in the dashboard, the token expired (>2 min old), or the token was already consumed. Always generate a fresh token at submit time. |
| Tokens are generated but the call still fails verification | Double-check that the domain hosting your form is listed in the reCAPTCHA admin console for this key. v3 rejects tokens from non-allow-listed origins. |
| You want different secrets per environment | Use one reCAPTCHA key pair per environment (production / staging) and save each Location's appropriate secret in its dashboard settings. |
