This guide will walk you through setting up a function that can make requests to external systems using webhooks.
This guide assumes you have already created a Data Connection source and a webhook. For more information, see the documentation how to create a data connection source and webhook.
Before following this guide, make sure you already created a Functions repository and understand how to write and publish Functions as described in our tutorial.
To use a webhook in Functions, the backing REST API source of the webhook must first be imported into the repository. Select the Resource imports side panel to the left of the screen to view the sources imported into the repository. Select Add > Sources to display a search dialog where you may select the source you want to import. Only sources with API names may be imported through this dialog.
Source imports into Function repositories work differently than source imports to Python transforms repositories and compute modules. Function repositories that import a given source will not be displayed in the list of repositories shown on the source overview. Any user with Viewer
access to a source will be able to import and use those webhooks in external Functions.
Once you import the REST API source to the Functions repository, it will be available in the TypeScript environment and accessible through the namespace of the source:
Copied!1
import { Sources } from "@foundry/external-systems";
If you get the error Cannot find module '@foundry/external-systems' or its corresponding type declarations.
, ensure the value for enableExternalSystems
is set to true
in the functions-typescript/functions.json
file. Once you update it and commit the changes, the system should install the necessary packages, including @foundry/external-systems
.
In the example below, we will explain how to make multiple calls to the dictionary API using a single Function.
If your Function does not make any Ontology edits, you will create a @Query()
function. If you would like to make Ontology edits, it would instead require the @OntologyEditFunction
decorator. Learn more about making Ontology edits from Functions in our documentation.
Using the standard TypeScript async/await pattern ↗, multiple webhook calls can be made simultaneously from a Function. Check the success of calls using the isOk
helper function exported from @foundry/functions-api
.
The following Function accepts a list of words as a TypeScript string array and makes one call for each word:
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
import { Sources } from "@foundry/external-systems"; import { OntologyEditFunction, isOk } from "@foundry/functions-api"; export class MyFunctions { @OntologyEditFunction() public async defineWords(words: string[]): Promise<void> { const results = await Promise.all(words.map(word => Sources.MyDictionarySource.webhooks.GetDefinition.call({ wordToDefine: word }))); results.forEach((result, i) => { if (isOk(result)) { const output = result.value.output; output.dictionary_definitions.forEach(definitions_for_word => { definitions_for_word.meanings.forEach(meaning => { meaning.definitions.forEach(def_for_part_of_speech => { console.log(`Found a ${meaning.partOfSpeech} definition for "${words[i]}": ${def_for_part_of_speech.definition}`); }) }) }); } }); } }
Log output for an input of ["tuba", "cool"]
:
LOG [2023-07-28T03:16:22.968Z] Found a noun definition for "tuba": A large brass musical instrument, usually in the bass range, played through a vibration of the lips upon the mouthpiece and fingering of the keys.
LOG [2023-07-28T03:16:22.968Z] Found a noun definition for "tuba": A type of Roman military trumpet, distinct from the modern tuba.
LOG [2023-07-28T03:16:22.968Z] Found a noun definition for "tuba": A large reed stop in organs.
LOG [2023-07-28T03:16:22.968Z] Found a noun definition for "tuba": A Malayan plant whose roots are a significant source of rotenone, Derris malaccensis.
LOG [2023-07-28T03:16:22.968Z] Found a noun definition for "tuba": A reddish palm wine made from coconut or nipa sap.
LOG [2023-07-28T03:16:22.968Z] Found a noun definition for "tuba": A tube or tubular organ.
LOG [2023-07-28T03:16:22.968Z] Found a noun definition for "cool": A moderate or refreshing state of cold; moderate temperature of the air between hot and cold; coolness.
LOG [2023-07-28T03:16:22.968Z] Found a noun definition for "cool": A calm temperament.
To help mitigate failures when working with a networked system, Functions expose errors propagated from webhooks using Result objects, which give information about the kind of error that occurred:
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
import { Sources } from "@foundry/external-systems"; import { OntologyEditFunction, isOk } from "@foundry/functions-api"; export class MyFunctions { @OntologyEditFunction() public async defineWords(words: string[]): Promise<void> { const results = await Promise.all(words.map(word => Sources.MyDictionarySource.webhooks.GetDefinition.call({ wordToDefine: word }))); results.forEach((result, i) => { if (isOk(result)) { // Extract the response } else { const errorName = result.error.name; if (errorName === "WebhookExecutionFailedToStart") { console.log("We were unable to initiate a request to the dictionary API."); } else if (errorName === "ParsingResponseFailed") { console.log("The external request succeeded, but the response couldn't be parsed."); } else { console.log("Something went wrong."); } } }); } }
When handling errors, authored code should listen for specific names and react accordingly. Functions currently return the following errors:
Error | Description |
---|---|
WebhookExecutionFailedToStart | The webhook failed to start. If this error is returned, it can be safely assumed that no request was made to the external system. |
WebhookExecutionTimedOut | The webhook execution began, but no response was received from the external system within the configured webhook time limit. |
RemoteRestApiReturnedError | The external system returned an error. Only returned for webhooks configured on a REST API source. |
RemoteApiReturnedError | The external system returned an error. Only returned for webhooks configured on a non-REST API source. |
ParsingResponseFailed | The webhook execution was successful, but the response from the external system could not be successfully parsed. This can happen if, for example, the response from the external system did not contain an expected field. Since the result of a webhook call will not necessarily be used, it is up to the application builder whether this should marked as a failure to end users. |
ServerError | An internal problem occurred within the webhooks service or the connector. |
UnknownError | An error occurred which could not be directly attributed to any Foundry service. |
This list of error types may change; users should structure their code to include a default case in the event that the Function executor returns an error with a new name.
The following code describes how to handle multiple webhook calls where some succeed and some fail within the same function. In our example, a RemoteRestApiReturnedError
is returned in the event that the dictionary server cannot find the definition for a given word.
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
import { Sources } from "@foundry/external-systems"; import { OntologyEditFunction, isOk } from "@foundry/functions-api"; export class MyFunctions { @OntologyEditFunction() public async defineWords(words: string[]): Promise<void> { const results = await Promise.all(words.map(word => Sources.MyDictionarySource.webhooks.GetDefinition.call({ wordToDefine: word }))); results.forEach((result, i) => { if (isOk(result)) { const output = result.value.output; output.dictionary_definitions.forEach(definitions_for_word => { definitions_for_word.meanings.forEach(meaning => { meaning.definitions.forEach(def_for_part_of_speech => { console.log(`Found a ${meaning.partOfSpeech} definition for "${words[i]}": ${def_for_part_of_speech.definition}`); }) }) }); } else { if (result.error.name === "RemoteRestApiReturnedError") { console.log(`ERROR: ${words[i]} could not be defined`, result.error.message); } } }); } }
Inputting ["asdf", "shire"]
to the above Function returns the following result:
LOG [2023-07-28T15:38:47.263Z] ERROR: asdf could not be defined Request returned an unsuccessful response code: 404 Response body: {"title":"No Definitions Found","message":"Sorry pal, we couldn't find definitions for the word you were looking for.","resolution":"You can try the search again at later time or head to the web instead."}
LOG [2023-07-28T15:38:47.264Z] Found a noun definition for "shire": Physical area administered by a sheriff.
LOG [2023-07-28T15:38:47.264Z] Found a noun definition for "shire": Former administrative area of Britain; a county.
LOG [2023-07-28T15:38:47.264Z] Found a noun definition for "shire": The general area in which a person lives or comes from, used in the context of travel within the United Kingdom.
LOG [2023-07-28T15:38:47.264Z] Found a noun definition for "shire": A rural or outer suburban local government area of Australia.
LOG [2023-07-28T15:38:47.264Z] Found a noun definition for "shire": A shire horse.
LOG [2023-07-28T15:38:47.264Z] Found a verb definition for "shire": To (re)constitute as one or more shires or counties.
Currently, there are no limits to the number of requests that can be made from within an Ontology edit Function, but existing Functions resource limits still apply. Webhook limits are also enforced.
Functions currently support webhooks with the following input and output types:
When calling webhooks from a @Query
function, the webhook must perform only Read API
calls that do not mutate the external system. Query functions are frequently retried or silently executed on pageload, and thus do not provide the same level of structured deliberate execution that is possible with an @OntologyEditFunction
. When configuring a webhook, you can specify whether it is safe to execute from a Query function by using the option for Read API
or Write API
.
OR
type as an input or output parameter are currently not supported. No code will be generated for those webhooks.Functions and webhooks have versions, and callers may invoke any version of a Function or webhook. When a Function is published, the most recent webhook version available at that time will be pinned to it.
When a Functions repository is opened in the Code Repositories application, the generated code bindings used for autocomplete will always use the most recent version of the webhook. This webhook version is displayed in the Resource imports side panel to the left.
Make sure your webhook is stable before publishing Functions that rely on its functionality.
Remember to republish the Function and bump users to new versions when changes are made to the webhook or Function. Previously published, pinned versions of the Function will still be available for use.
The following table summarizes the permissions that are required to author, publish, and consume external functions.
Action | User | Permission required |
---|---|---|
Import source to a Functions repository | Function editor | source:viewer on the source, which is granted to the Viewer default role. |
Publish Function invoking a webhook | Function editor | webhooks:execute permission on the source, which is only granted to Owner and Editor default roles. |
Configure an Action to use an @OntologyEditFunction() that calls webhooks | Action editor | webhooks:grant-action-validated-execution permission on the webhook and Viewer permission on the Function |
Execute a @Query() webhook from Workshop | End user | webhooks:execute permission on the source, which is only granted to Owner and Editor default roles. |
Execute an @OntologyEditFunction() from an Action | End user | The user must meet the submission criteria for the Action. No permissions on the source, webhook, or Function are checked in this case. Users creating and managing Actions must ensure that submission criteria are configured appropriately. |
Use the following platform tools to gain more insight into webhook executions from Functions:
We recommend the following best practices when using external sources to call webhooks from Functions:
record
type parameters with expected fields, if possible. Using explicit types, rather than JSON, means that Function code is less likely to throw unexpected runtime errors.isOk
and isErr
built-in functions exported from @foundry/functions-api
to check for success and error states, and narrow down the type of error through the name field.