Back to Docs

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:

Basic contact form
<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

1

Create a form token

In your Dispatch dashboard, go to Brand Settings → Channels → Web Forms

2

Add the form to your site

Copy the form endpoint and add it to your HTML

3

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:

FieldMaps ToNotes
emailRequester emailRecommended for replies
subjectTicket titleOr use message
messageTicket bodyMain content

Form Attributes

AttributeValueRequired
actionYour form endpoint URLYes
methodPOSTYes
enctypemultipart/form-dataOnly for file uploads

Field Mapping

These field names are automatically recognized and mapped to ticket properties:

Form Field NameTicket Property
emailcustomFields.requesterEmail
namecustomFields.requesterName
subjecttitle
message or bodybody
prioritypriority (low, normal, high, urgent)
Any other fieldcustomFields.{fieldName}

Custom Fields Example

Form with custom fields
<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:

The file field must be named attachments - any other name (like files, upload, or screenshot_0) will cause an error or be ignored.
Form with file uploads
<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

RequirementValue
Field nameattachments (required, exact match)
Form encodingenctype="multipart/form-data"
Max files per submission5 files
Max file size20 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
Files with unsupported types are silently skipped. The form submission succeeds, but those files won't be attached. Users won't see an error, so consider validating file types on your end before submission if you need to warn users.

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-you

If 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/error

The 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.com

Leave 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.

Two critical requirements for JavaScript submissions:
  1. Include Accept: application/json header - Without it, you'll get a redirect instead of JSON
  2. Configure allowed origins - JavaScript submissions will fail with CORS errors unless your domain is in the form's allowed origins list
JSON submission
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

FormData submission with files
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

React form component
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 response
{
  "success": true,
  "ticket": {
    "id": "tkt_abc123",
    "ticketNumber": 1042
  }
}
Error response
{
  "success": false,
  "error": "Form submissions are disabled"
}
Without the 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

StatusMeaningCommon Causes
200SuccessTicket created successfully
400Bad RequestFile too large, too many files, wrong file field name
403ForbiddenForm disabled, origin not allowed (CORS)
404Not FoundInvalid form token
429Too Many RequestsRate limit exceeded (100 requests/minute per IP)
500Server ErrorInternal error - see troubleshooting below

Error Messages

ErrorSolution
"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:

Honeypot spam protection
<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, not files, upload, or screenshot_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:

  1. Go to Brand Settings → Channels → Web Forms
  2. Edit your form token
  3. Add your domain to "Allowed Origins" (include the protocol: https://yoursite.com)
  4. For local development, also add http://localhost:3000
Standard HTML form submissions (not JavaScript) work without CORS configuration because browsers handle them differently.

Custom fields not appearing in ticket

All form fields are accepted and stored. Standard fields map to ticket properties:

  • email → customFields.requesterEmail
  • name → customFields.requesterName
  • subject → title
  • message or body → 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:

Basic test
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"
Test with file upload
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]"

Ready to add a form?

Create a form token in your dashboard and start receiving tickets.