Docs

Get notified when a form is submitted

React to new submissions in your own system, with an email hook through Make or Zapier, or by polling the API.

Webhooks are coming soon

Webhooks are coming soon. Tell us your use case and we'll let you know when they launch.

Until then, there are two ways for your system to learn that someone submitted a form:

RecipeHow it worksChoose it when
Email hookThe form emails each submission to an inbound address of Make or Zapier, which passes it on to your system.You want to react within a minute, and you already use Make or Zapier, or can.
PollingYour server asks the API every few minutes for entries that changed.You want no third party in the path, or a safety net that catches everything.

Many integrations use both: the email hook for speed, and polling to catch anything the hook missed.

Recipe 1: an email hook

A form can send emails after each submission, and a rule can send one to any address. Make and Zapier each give you an inbound email address that starts a scenario (a Zap, in Zapier) whenever an email arrives. Point a rule at that address, with the entry in the body as JSON, and the scenario receives every submission.

The email rule

The rule sends a plain-text email whose body is the entry's data as JSON:

SettingValue
ToYour inbound address, such as <id>@hook.eu1.make.com
Content typePlain text (טקסט פשוט). The default, HTML, escapes the quotes in the JSON and wraps it in HTML, so the scenario can't parse it.
Body{{ model | json }}

{{ model | json }} turns the submitted data into JSON. To learn which entry it came from, include the entry's id as well:

{{ { entryId: data.entry.id, data: model } | json }}

The rule belongs to the form's after-submit rules. The Tepez team usually sets it up for you: send us the inbound address. Anyone who edits your forms can also add it in the form's settings, on the After submission tab, under Send emails. GET /form/{formId} shows it under postSubmit.emails:

"postSubmit": {
  "emails": [
    {
      "to": ["<id>@hook.eu1.make.com"],
      "replyTo": ["you@example.com"],
      "subject": "Tepez entry {{ data.entry.id }}",
      "text": "{{ { entryId: data.entry.id, data: model } | json }}"
    }
  ]
}

A property named text holds a plain-text body, and html an HTML one. On each submission, the inbound address receives an email like this one:

From: Tepez <noreply@tepez.co.il>
Subject: Tepez entry <entryId>
Content-Type: text/plain; charset=UTF-8

{
  "entryId": "<entryId>",
  "data": {
    "fullName": "Dana Levi",
    "email": "dana@example.com",
    "position": null,
    "address": {
      "city": "Haifa",
      "street": "Herzl 10"
    },
    "phone": "050-1234567"
  }
}
  • data holds every field of the form. A field the person left empty is null here, while the stored entry leaves it out.
  • json indents the JSON by 2 spaces. json:0 writes it on one line.
  • The entry records each email it sent, body included, in sentEmails. Read it with GET /entry/{entryId} to see exactly what was sent.

A Make scenario

  1. In Make, create a scenario that starts with Webhooks > Custom mailhook. Make shows the mailhook's email address. Copy it.
  2. Have the email rule above added to your form, sending to that address.
  3. Submit a test entry, so Make learns the email's structure.
  4. Add JSON > Parse JSON, and map the email's text content to its JSON string.
  5. Add HTTP > Make a request: GET https://api.tepez.co.il/v1.0/entry/{{entryId}}, with the header x-api-key set to your API key, and parse the response. It returns the entry as the API stores it, as in Read one entry.
  6. Add your own steps: create a record in your CRM, call your API, add a row to a sheet.

Zapier works the same way, with an inbound email address from Email by Zapier as the trigger.

Treat the email as a trigger, not as the data

The email carries no signature, so your scenario can't prove that it came from Tepez: anyone who learns the inbound address can send it an email that looks the same. Use the email only to learn which entry changed, and read the entry itself from the API with your key, as step 5 does. Check that its form is the form you expect and that its status is submitted before you act on it.

Tepez sends each email once, right after the submission, and doesn't retry. If it's lost on the way, your scenario never hears about that entry. Poll as well if you must not miss one.

Recipe 2: poll for changes

Your server asks GET /entry for the form's entries that changed since the last time it asked. Every entry has a changedAt time, which moves forward whenever anything about the entry changes. Filter on it with $gt:

curl -G -i https://api.tepez.co.il/v1.0/entry \
  -H "x-api-key: $TEPEZ_API_KEY" \
  --data-urlencode 'query={"form":"<formId>","status":{"$in":[2,3]},"changedAt":{"$gt":"2026-09-30T16:00:00.000Z"}}' \
  --data-urlencode 'fields=["id","status","changedAt","data"]' \
  --data-urlencode 'limit=100' \
  --data-urlencode 'deleteMarkers=true'

The response, trimmed:

{
  "data": [
    {
      "id": "<entryId>",
      "status": "submitted",
      "changedAt": "2026-09-30T16:47:24.193Z",
      "data": {
        "fullName": "Dana Levi",
        "email": "dana@example.com",
        "address": { "city": "Haifa", "street": "Herzl 10" },
        "phone": "050-1234567"
      }
    },
    { "_id": "<deletedEntryId>", "deletedAt": "2026-09-30T16:50:41.918Z" }
  ]
}

The parameters:

  • query picks the form's entries whose changedAt is later than your cursor. status 2 is submitted and 3 is deleted. Keep 3: without it, entries deleted since your last run never show up. Query and export entries lists every filter query accepts.
  • fields keeps each item small. Leave it out to get whole entries, with their PDFs and emails.
  • limit sets the page size, up to 2,000 (see Limits). Always send it.
  • deleteMarkers=true returns each deleted entry as a marker instead of skipping it.

How to read the results:

  • Follow the Link header until a response has none. When more items match than limit, the response carries Link: <https://api.tepez.co.il/v1.0/entry?after=<cursor>&…>; rel=next, the URL of the next page. The code above follows it. See Page through the results.
  • An item with deletedAt is a deletion marker. It has only _id and deletedAt: note that the id is under _id, not id. Remove the entry on your side if you have it.
  • Ignore markers for ids you don't know. The first page also lists markers for entries that were removed from your account in other ways, for example together with a deleted form, whatever your query asks for.
  • Update, don't just insert. changedAt moves on every change to an entry, including an edit after it was submitted and a PDF archived after the submit. So the same entry comes back each time it changes. Store entries by id, and overwrite the stored copy.
  • Keep the cursor from the data, not from your clock. Items come in the order the entries were created, not by changedAt, so take the largest changedAt or deletedAt across all pages, as the code does. To be safe against changes that finish out of order, start the next run a minute before the saved cursor. Storing by id makes the overlap harmless.
  • Poll every few minutes. Each run returns only what changed, so it stays small.

Troubleshooting

ResponseCause and fix
The scenario receives the email but can't parse itThe rule's content type is HTML. Change it to plain text, so the body is the JSON alone.
No email reaches the inbound addressThe rule's to isn't a valid address, or its condition didn't match. Read the entry's sentEmails: an email that was sent is listed there.
400 "query.status" must be one of [object, 0, 1, 2, 3, 4, 5]query takes the status number, not its label: {"$in":[2,3]}, not "submitted".
400 "query" must be of type objectquery isn't valid JSON. Quote the keys and the $gt and $in operators.
The same entry comes back on every runYour cursor doesn't move forward. Save the largest changedAt or deletedAt of each run, across all pages.
Deleted entries never show upquery has "status":2 alone, or deleteMarkers=true is missing.
A marker's id isn't in your systemIt belongs to an entry removed in another way, for example with a deleted form, or to one you never stored. Ignore it.

Errors explains the error format and lists more messages.

Every response from the API carries an x-tepez-request-id header. Quote it when you contact us.

Email support with the request id

Question about this page? Email support@tepez.co.il

On this page