Web Forms
Add a contact form to your website that creates support tickets. No JavaScript required.
Quick Start
Create a form token in your Dispatch dashboard, then add a simple HTML form to your site:
<form action="https://dispatch-tickets-api.onrender.com/v1/forms/ft_xxxxx" method="POST">
<input type="email" name="email" placeholder="Your email" required>
<input type="text" name="name" placeholder="Your name">
<input type="text" name="subject" placeholder="Subject" required>
<textarea name="message" placeholder="How can we help?" required></textarea>
<button type="submit">Submit</button>
</form>That's it. When users submit the form, a ticket is created and they're redirected to a thank you page.
How It Works
Create a form token
In your Dispatch dashboard, go to Brand Settings → Channels → Web Forms
Add the form to your site
Copy the form endpoint and add it to your HTML
User submits the form
A ticket is created and the user sees a confirmation
Requirements
Minimum Required Fields
Your form needs at least one of these to create a meaningful ticket:
| Field | Maps To | Notes |
|---|---|---|
email | Requester email | Recommended for replies |
subject | Ticket title | Or use message |
message | Ticket body | Main content |
Form Attributes
| Attribute | Value | Required |
|---|---|---|
action | Your form endpoint URL | Yes |
method | POST | Yes |
enctype | multipart/form-data | Only for file uploads |
Field Mapping
These field names are automatically recognized and mapped to ticket properties:
| Form Field Name | Ticket Property |
|---|---|
email | customFields.requesterEmail |
name | customFields.requesterName |
subject | title |
message or body | body |
priority | priority (low, normal, high, urgent) |
| Any other field | customFields.{fieldName} |
Custom Fields Example
<form action="https://dispatch-tickets-api.onrender.com/v1/forms/ft_xxxxx" method="POST">
<input type="email" name="email" required>
<input type="text" name="name">
<input type="text" name="subject" required>
<textarea name="message" required></textarea>
<!-- Custom fields - stored in ticket.customFields -->
<input type="text" name="company" placeholder="Company name">
<input type="text" name="phone" placeholder="Phone number">
<select name="department">
<option value="sales">Sales</option>
<option value="support">Support</option>
<option value="billing">Billing</option>
</select>
<button type="submit">Submit</button>
</form>File Uploads
Allow users to attach files to their tickets. Add a file input and set the form encoding:
attachments - any other name (like files, upload, or screenshot_0) will cause an error or be ignored.<form action="https://dispatch-tickets-api.onrender.com/v1/forms/ft_xxxxx"
method="POST"
enctype="multipart/form-data">
<input type="email" name="email" required>
<input type="text" name="subject" required>
<textarea name="message" required></textarea>
<!-- IMPORTANT: Field name must be "attachments" -->
<input type="file" name="attachments" multiple>
<button type="submit">Submit</button>
</form>File Upload Requirements
| Requirement | Value |
|---|---|
| Field name | attachments (required, exact match) |
| Form encoding | enctype="multipart/form-data" |
| Max files per submission | 5 files |
| Max file size | 20 MB per file |
Allowed File Types
The following file types are accepted:
- Images (JPEG, PNG, GIF, WebP, SVG, etc.)
- Documents (PDF, Word, Excel, PowerPoint)
- Text files (TXT, CSV, JSON, XML, etc.)
- Archives (ZIP)
- Audio and video files
Form Options
Configure these options when creating your form token in the dashboard:
Success URL
Redirect users to your own thank you page after successful submission:
Success URL: https://yoursite.com/thank-youIf not set, users see a built-in thank you page with their ticket reference number.
Error URL
Redirect users to your own error page if submission fails:
Error URL: https://yoursite.com/errorThe error message is appended as a query parameter: ?error=Form+submission+failed
Allowed Origins (CORS)
Restrict which domains can submit to your form:
Allowed Origins:
https://yoursite.com
https://www.yoursite.comLeave empty to allow submissions from any origin. When set, only listed origins can submit.
JavaScript Integration
For more control, submit forms via JavaScript using the Fetch API.
- Include
Accept: application/jsonheader - Without it, you'll get a redirect instead of JSON - Configure allowed origins - JavaScript submissions will fail with CORS errors unless your domain is in the form's allowed origins list
fetch("https://dispatch-tickets-api.onrender.com/v1/forms/ft_xxxxx", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json" // Required for JSON response!
},
body: JSON.stringify({
email: "[email protected]",
name: "John Doe",
subject: "Help needed",
message: "I need assistance with..."
})
})
.then(res => res.json())
.then(data => {
if (data.success) {
console.log("Ticket created:", data.ticket.ticketNumber);
} else {
console.error("Error:", data.error);
}
});With File Uploads
const formData = new FormData();
formData.append("email", "[email protected]");
formData.append("name", "John Doe");
formData.append("subject", "Help needed");
formData.append("message", "Please see the attached screenshot.");
// Add files - field name MUST be "attachments"
const fileInput = document.querySelector('input[type="file"]');
for (const file of fileInput.files) {
formData.append("attachments", file);
}
fetch("https://dispatch-tickets-api.onrender.com/v1/forms/ft_xxxxx", {
method: "POST",
headers: { "Accept": "application/json" }, // Required!
body: formData // Don't set Content-Type - browser sets it with boundary
})
.then(res => res.json())
.then(data => console.log(data));React/Next.js Example
function ContactForm({ formToken }) {
const [status, setStatus] = useState('idle'); // idle, loading, success, error
const [error, setError] = useState(null);
async function handleSubmit(e) {
e.preventDefault();
setStatus('loading');
setError(null);
const formData = new FormData(e.target);
try {
const res = await fetch(
`https://dispatch-tickets-api.onrender.com/v1/forms/${formToken}`,
{
method: 'POST',
headers: { 'Accept': 'application/json' },
body: formData,
}
);
const data = await res.json();
if (data.success) {
setStatus('success');
} else {
setError(data.error || 'Submission failed');
setStatus('error');
}
} catch (err) {
setError('Network error. Please try again.');
setStatus('error');
}
}
if (status === 'success') {
return <p>Thanks! We'll be in touch soon.</p>;
}
return (
<form onSubmit={handleSubmit} encType="multipart/form-data">
<input type="email" name="email" required placeholder="Email" />
<input type="text" name="subject" required placeholder="Subject" />
<textarea name="message" required placeholder="Message" />
<input type="file" name="attachments" multiple />
{error && <p className="error">{error}</p>}
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Sending...' : 'Submit'}
</button>
</form>
);
}Response Format
When you include Accept: application/json, you get a JSON response:
{
"success": true,
"ticket": {
"id": "tkt_abc123",
"ticketNumber": 1042
}
}{
"success": false,
"error": "Form submissions are disabled"
}Accept: application/json header, the API returns a redirect (302) to the success or error URL instead of JSON. This is the expected behavior for standard HTML form submissions.Error Handling
The API returns different HTTP status codes depending on the error type:
HTTP Status Codes
| Status | Meaning | Common Causes |
|---|---|---|
| 200 | Success | Ticket created successfully |
| 400 | Bad Request | File too large, too many files, wrong file field name |
| 403 | Forbidden | Form disabled, origin not allowed (CORS) |
| 404 | Not Found | Invalid form token |
| 429 | Too Many Requests | Rate limit exceeded (100 requests/minute per IP) |
| 500 | Server Error | Internal error - see troubleshooting below |
Error Messages
| Error | Solution |
|---|---|
| "Form not found" | Check the form token in your URL is correct |
| "Form submissions are disabled" | Enable the form in your dashboard settings |
| "Origin not allowed" | Add your domain to the form's allowed origins list |
| "File too large. Maximum size is 20MB per file." | Compress or resize files before uploading |
| "Too many files. Maximum is 5 files per submission." | Reduce the number of files or submit multiple times |
| "Unexpected file field. Use 'attachments' as the field name." | Rename your file input to name="attachments" |
Rate Limits
Form submissions are rate-limited to prevent abuse:
- 100 requests per minute per IP address
- Exceeding this returns a 429 status code
- Wait 1 minute before retrying
Spam Protection
Honeypot Field
Add a hidden field that bots will fill out but humans won't. Configure the honeypot field name in your form settings, then add a hidden input:
<form action="https://dispatch-tickets-api.onrender.com/v1/forms/ft_xxxxx" method="POST">
<!-- Honeypot - hide with CSS, bots will fill it -->
<input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">
<input type="email" name="email" required>
<textarea name="message" required></textarea>
<button type="submit">Submit</button>
</form>If the honeypot field contains any value, the form submission silently "succeeds" without creating a ticket. Bots think they succeeded while real users are unaffected.
Rate Limiting
Form submissions are rate-limited to prevent abuse. Excessive submissions from the same IP address will be temporarily blocked.
Troubleshooting
Common integration issues and how to fix them:
Getting 500 errors with no message
A 500 error usually means something went wrong server-side. Common causes:
- Wrong file field name - Must be exactly
attachments, notfiles,upload, orscreenshot_0 - Missing enctype - When uploading files, form must have
enctype="multipart/form-data" - Invalid form token - Double-check the token in your URL
To debug, test with curl to see the full error response:
curl -X POST "https://dispatch-tickets-api.onrender.com/v1/forms/ft_xxxxx" \
-H "Accept: application/json" \
-d "[email protected]" \
-d "message=test"File uploads not working
Check these three requirements:
<!-- All three are required for file uploads -->
<form action="..." method="POST" enctype="multipart/form-data">
<!-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1. enctype -->
<input type="file" name="attachments" multiple>
<!-- ^^^^^^^^^^^ 2. exact field name -->
</form>
<!-- 3. Files must be < 20MB and supported types -->JavaScript submission returns redirect instead of JSON
You're missing the Accept: application/json header:
// Wrong - will redirect
fetch(url, { method: 'POST', body: formData })
// Correct - will return JSON
fetch(url, {
method: 'POST',
headers: { 'Accept': 'application/json' },
body: formData
})CORS errors in browser console
JavaScript submissions require CORS configuration. In your dashboard:
- Go to Brand Settings → Channels → Web Forms
- Edit your form token
- Add your domain to "Allowed Origins" (include the protocol:
https://yoursite.com) - For local development, also add
http://localhost:3000
Custom fields not appearing in ticket
All form fields are accepted and stored. Standard fields map to ticket properties:
email→ customFields.requesterEmailname→ customFields.requesterNamesubject→ titlemessageorbody→ body- Everything else → customFields.{fieldName}
If you're not seeing custom fields, check the ticket's customFieldsobject in the API response or admin UI.
Form submits but user sees API URL
Configure a Success URL in your form settings to redirect users to your own thank you page. Without it, users see the built-in thank you page (not an error).
Honeypot not blocking bots
Make sure the honeypot field name in your form settings matches the field in your HTML, and that the field is hidden with CSS (not type="hidden"):
<!-- Correct - hidden with CSS -->
<input type="text" name="_gotcha" style="display:none" tabindex="-1">
<!-- Wrong - type="hidden" won't catch bots -->
<input type="hidden" name="_gotcha">Testing Your Form
Test your form with cURL to verify it's working:
curl -X POST "https://dispatch-tickets-api.onrender.com/v1/forms/ft_xxxxx" \
-d "[email protected]" \
-d "subject=Test ticket" \
-d "message=This is a test submission"curl -X POST "https://dispatch-tickets-api.onrender.com/v1/forms/ft_xxxxx" \
-F "[email protected]" \
-F "subject=Test with attachment" \
-F "message=Please see attached" \
-F "[email protected]"