Triggers and sources
Choose what starts a workflow (a button, a schedule, a webhook call or new rows) and where its rows come from (a Google Sheet, a CSV or XLSX file, Airtable or a webhook).
The first two steps of a workflow say when it runs and what data it uses. The trigger starts a run. The source gives the rows, and each row becomes one render. To set them, open the workflow in the builder and click the Trigger or Source step.
Triggers
Pick one under Start in the trigger step.
| Trigger | When the workflow runs |
|---|---|
| Run Now | Only when you press Run Now (or start it through the API). |
| Schedule | Every hour, day, week or month, or on your own cron schedule. |
| Incoming webhook | When your app sends JSON to the workflow’s secret URL. |
| New or changed rows | When the source has rows it hasn’t seen before. |
Only active workflows run on their trigger. A draft runs only when you press Run Now or Test with 1 row, or when your code starts it through the API. See Active and draft.
Run Now
The workflow runs only when someone presses Run Now or Test with 1 row in the builder, or when
your code calls POST /v1/workflows/{id}/run. See the Workflows API.
Schedule
- Under How often, pick Every hour, Every day, Every week, Every month or Custom (cron).
- Fill in the time:
- Every hour: Minutes past the hour, from 0 to 59.
- Every day: the Time.
- Every week: the Day and the Time.
- Every month: the Day of the month (1–28) and the Time. Days stop at 28 so every month has the day.
- Custom (cron): a Cron expression (below).
- Pick the Time zone. It starts with your computer’s time zone.
- Save, then click Activate.
The builder shows the next run under its top bar, and the workflow card shows it too.
A cron expression has five parts: minute, hour, day of the month, month and day of the week. For example,
0 9 * * 1-5 runs at 9:00 on weekdays, and 30 8 1 * * runs at 8:30 on the first day of each month. A
schedule can run at most every 15 minutes.
Schedules follow the clock of your time zone, also when daylight saving time starts or ends. A time that doesn’t exist that day (the clocks jump forward) runs just after. A time that happens twice (the clocks go back) runs once.
Incoming webhook
Your app, website or another service starts a run by sending JSON to the workflow’s secret URL.
- Under Start, pick Incoming webhook. The source changes to Webhook payload if you haven’t set up a source yet.
- Click Save. The trigger step now shows the URL.
- Click Copy and use the URL in your app.
- Click Activate. A draft refuses webhook calls.
The URL looks like https://app.jamframe.com/api/public/workflows/hooks/wfh_…. Send a POST request
with a JSON body:
curl -X POST "$WORKFLOW_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '[{"name": "Ada", "role": "Engineer"}, {"name": "Grace", "role": "Admiral"}]'const res = await fetch(process.env.WORKFLOW_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify([
{ name: "Ada", role: "Engineer" },
{ name: "Grace", role: "Admiral" },
]),
});
console.log(res.status, await res.json()); // 202 and the run's idimport os
import requests
res = requests.post(
os.environ["WORKFLOW_WEBHOOK_URL"],
json=[
{"name": "Ada", "role": "Engineer"},
{"name": "Grace", "role": "Admiral"},
],
)
print(res.status_code, res.json()) # 202 and the run's idHow the body becomes rows is under Webhook payload. Limits:
- Up to 256 KB per call, and up to 1,000 rows. Extra rows are left out.
- Up to 60 calls a minute for one workflow.
| Answer | What it means |
|---|---|
202 |
The run is queued. The body has the run’s id: {"run": {"id": "…", "status": "queued"}}. |
400 |
The body isn’t JSON or is empty, or the workflow isn’t complete. |
402 |
Your plan’s runs for this month, or your render credits, are used up. |
404 |
The URL is wrong, or it was replaced with New URL. |
409 |
The workflow is a draft. Activate it. |
413 |
The body is larger than 256 KB. |
429 |
Too many calls. Wait the number of seconds in the Retry-After header. |
Only people with editor access see the URL. Your code can also get a new URL with
POST /v1/workflows/{id}/webhook-token; see the Workflows API.
You can use the incoming webhook trigger with another source too, such as a Google Sheet. A call then
only starts the run, and the rows come from the source. The body must still be a JSON object or array
that isn’t empty, such as {"start": true}.
New or changed rows
The workflow checks the source on a timer and renders only rows it hasn’t seen before.
- Under Start, pick New or changed rows.
- Under Check the source, pick how often: Every 15 minutes, Every 30 minutes, Every hour, Every 3 hours, Every 6 hours, Every 12 hours or Once a day.
- Save, then click Activate.
How it decides what is new:
- The first check only notes the rows already there. Nothing renders, so switching it on doesn’t render your whole sheet.
- After that, each check renders the rows it hasn’t seen. A row whose values changed counts as new and renders again.
- A check that finds nothing new doesn’t count as a run for your plan.
- Rows left out because of your plan’s rows-per-run limit are marked as seen, so a later check doesn’t pick them up.
Because a changed row counts as new, a filter works well with this trigger. For example, filter on
Status is Ready: when you change a row’s status to Ready, the next check renders it. See
Filter rows.
This trigger works with a Google Sheets, CSV or XLSX, or Airtable source. It doesn’t work with a webhook payload.
Sources
Pick one under Read rows from in the source step. Every source gives rows with named columns:
- In a sheet or a file, the first row holds the column names. Every row after it is one row of data.
- Empty rows are skipped. An empty column name becomes
Column 1,Column 2and so on. A repeated name gets a number, such asPrice (2). - At most 5,000 rows and 100 columns are read, and up to 5,000 characters per cell.
To check what a source gives, click Load preview under Preview & mapping. See Mapping and transforms.
Google Sheets
JamFrame reads a Google Sheet that is shared by link. You don’t need to sign in to Google.
- In Google Sheets, click Share.
- Under General access, choose Anyone with the link, with the role Viewer.
- Copy the sheet’s link from your browser’s address bar, or click Copy link.
- In the source step, paste it under Sheet link.
- Leave Google account set to None: read the public link.
The link looks like https://docs.google.com/spreadsheets/d/…/edit#gid=0. The number after gid= is the
tab, and that tab is read. To pick a tab by its name instead, paste the link without the #gid=… part and
type the name under Tab (optional). With neither, the first tab is read. A link from File →
Share → Publish to the web works too.
| Message | What to do |
|---|---|
| The sheet isn’t shared publicly. | Share the sheet as Anyone with the link → Viewer. |
| The sheet was not found. Check the link. | Copy the link again from the address bar. |
| That is not a Google Sheets link. | Use a link that starts with https://docs.google.com/spreadsheets/d/. |
CSV or XLSX file
JamFrame downloads a CSV or Excel (.xlsx) file from a link each time the workflow runs.
- Put the file somewhere it has a public, direct download link. The link must give the file itself, not a web page about it.
- In the source step, paste the link under File URL.
- Leave Format on Detect, or pick CSV or XLSX.
- An XLSX file is read from its first sheet. To read another sheet, pick XLSX under Format and type the sheet’s name under Sheet (optional).
Details:
- The file can be up to 10 MB.
- CSV files can use commas, semicolons or tabs between values. JamFrame finds out which.
- Dates in an XLSX file come out like
2026-11-10. - Each run downloads the file again, so update the file at the same link to change the rows.
If the link opens a web page instead of the file, the step says “The URL returned a web page, not a CSV or XLSX file.” Use the direct download link instead.
Airtable
JamFrame reads a table, or one view of it, with an Airtable personal access token.
- In Airtable, go to airtable.com/create/tokens and create a token.
Give it the scope
data.records:readand access to your base. - In the source step, fill in:
- Base id: it starts with
app. Find it in the base’s address:airtable.com/app…/tbl…. - Table name or id: for example
Listings. - View (optional): a view’s name, such as
Grid view, to read only its rows in its order. - Personal access token: paste the token. It is stored encrypted, and afterwards the box only shows Saved with its last characters. Type a new one to replace it.
- Base id: it starts with
Each record is one row. An attachment column gives the link to its first file, so you can map it to an image key. A collaborator gives the person’s name, and a list gives its items separated by commas.
| Message | What to do |
|---|---|
| Airtable refused the token. | Make a token with the data.records:read scope. |
| The Airtable token has no access to this base. | Add the base to the token’s access list. |
| The Airtable base or table was not found. | Check the base id and the table name. |
Webhook payload
The rows are the JSON that was sent to the incoming webhook. Use this source with the Incoming webhook trigger.
The body can be:
- an array of objects, one row each:
[{"name": "Ada"}, {"name": "Grace"}]; - an object that holds such an array under
rows,records,dataoritems:{"rows": [{"name": "Ada"}]}; - one object, which is one row:
{"name": "Ada"}.
Each key of an object is a column. A nested object becomes columns named parent.child: for example
{"user": {"name": "Ada"}} gives the column user.name. A list of plain values becomes one text with
commas.
To test without your app, press Run Now in the builder. A box asks for a Test payload. Type the JSON a call would send and click Run with this payload. Load preview uses the test payload, or the last call the webhook got.
What next
- Pick and order the rows, and match columns to keys: Mapping and transforms.
- Send each file somewhere and follow the runs: Delivery and runs.
Last updated
Still stuck?
Email [email protected] and a real person will reply. Tell us the template you were using and what you expected to happen.