Configure a submit Action
Inside your listener function, you can respond to any Event that occurs in the environment you’re listening to.
Your transformation and validation code is already responding to Record level Events, but a listener can respond to any Event, like the Action triggered by a button click.
Actions are defined in your Workbook Blueprint and backed by Jobs in your listener.
Adding behavior to the action
In this step of the tutorial, we’re going to configure our Workbook’s primary Action, defined in our Blueprint as the submitAction
.
The primary Action button is located in the upper right corner of your Workbook.

When this button is clicked by a user, a Job is created. We’re going to listen for that job:ready
event,
and call out to a waiting webhook to export our data.
To configure our listener to call to a webhook, let’s create a webhook. We recommend using Webhook.site. Simply navigate to their site and copy Your unique URL
.
Now, update the value of the WEBHOOK_SITE_URL
variable in your .env
file with the webhook url you just copied.
import api from "@flatfile/api";
import { responseRejectionHandler } from "@flatfile/util-response-rejection";
import axios from "axios";
export default function flatfileEventListener(listener) {
listener.on(
"job:ready",
{ job: "workbook:submitActionFg" },
async ({ context: { jobId, workbookId }, payload }) => {
const { data: workbook } = await api.workbooks.get(workbookId);
const { data: workbookSheets } = await api.sheets.list({ workbookId });
const sheets = [];
for (const [_, element] of workbookSheets.entries()) {
const { data: records } = await api.records.get(element.id);
sheets.push({
...element,
...records,
});
}
try {
await api.jobs.ack(jobId, {
info: "Starting job to submit action to webhook.site",
progress: 10,
});
console.log(JSON.stringify(sheets, null, 2));
const webhookReceiver =
process.env.WEBHOOK_SITE_URL ||
"https://webhook.site/c83648d4-bf0c-4bb1-acb7-9c170dad4388"; //update this
const response = await axios.post(
webhookReceiver,
{
...payload,
method: "axios",
workbook: {
...workbook,
sheets,
},
},
{
headers: {
"Content-Type": "application/json",
},
}
);
if (response.status === 200) {
const rejections = response.data.rejections;
if (rejections) {
const totalRejectedRecords = await responseRejectionHandler(
rejections
);
return await api.jobs.complete(jobId, {
outcome: {
next: {
type: "id",
id: rejections.id,
label: "See rejections...",
},
message: `Data was submission was partially successful. ${totalRejectedRecords} record(s) were rejected.`,
},
});
}
return await api.jobs.complete(jobId, {
outcome: {
message:
"Data was successfully submitted to webhook.site. Go check it out at " +
webhookReceiver +
".",
},
});
} else {
throw new Error("Failed to submit data to webhook.site");
}
} catch (error) {
console.log(`webhook.site[error]: ${JSON.stringify(error, null, 2)}`);
await api.jobs.fail(jobId, {
outcome: {
message:
"This job failed probably because it couldn't find the webhook.site URL.",
},
});
}
}
);
}
// See full code example (https://github.com/FlatFilers/flatfile-docs-kitchen-sink/blob/main/javascript/shared/workbook_submit.js)
Watch your Action
Check your listener is still running. If not, run npx flatfile@latest develop
.
Now, in your Workbook and click the “Submit” button in the top right corner.
Over on your Webhook.site browser tab look at the query log and observe that the webhook received a call; this is the Action running.
Next steps
Congratulations! You now now have an example listener that is performing transformations and validations in response to changes in your Workbook, and you’ve set up an action to export your cleaned data.
You’ve been running this listener locally while in development. Next, let’s see how easy it is to Deploy to Flatfile.