Skip to content

Custom integrations

An integration is one request to one endpoint. You give it a URL, say what goes into the request, and say which parts of the reply you want to keep. This page walks through the editor.

You’ll want the other service’s API documentation open, a credential if the endpoint needs one, and a sample response if you have one. To create or edit an integration you need the Tenant Admin role, or a custom role with the integration permissions.

Go to Settings > Integrations and click Create Integration. The editor has five tabs down the left, and a Test console on the right that stays put as you work. The test console can’t run anything until the integration has been saved, so fill in enough to save, then come back and test as you refine it.

Save with Create Integration at the bottom. After that the button says Save.

Name is what everyone sees, in the list and in the form builder. Make it what the button will do: “Line check”, “Address lookup”.

Slug is the short identifier, lowercase with hyphens. WorkWing uses it to refer to the integration, so pick it once and leave it alone.

Description and Icon are optional. The description shows to whoever maintains integrations later, so say what the call actually does.

Type stays on Custom.

Execution Mode is the important choice on this tab, and sync or async below explains it.

Active controls whether the integration can run at all. Turning it off frees a place against your plan’s allowance and leaves the configuration intact.

Timeout (minutes) applies to async integrations. It’s how long WorkWing waits for the other service to call back before it gives up on the run, from 1 minute to 1,440. The default is 60.

Max retry attempts is how many times WorkWing tries again when a request fails for a reason that might clear, such as the other service returning a server error or not answering. Leave it blank for the default of 3, or set anything from 1 to 10. A request the other service rejected outright is never retried, however you set this.

Strict response shape compares each reply’s structure, meaning its keys and the types of their values, against the last one that worked. If the structure changes, the run fails with a message saying so. Turn it on once the integration is known good, and you’ll hear about it the day the other service changes its API instead of finding empty fields weeks later.

Choosing Async adds two more:

  • Allow Partial Updates lets the other service send several callbacks that build up one result, rather than one final answer.
  • Completion Field names the field in the callback that tells WorkWing the work is finished. Without it, the first callback ends the run.

Method is GET, POST or PUT.

URL is the endpoint, and it must be https. It can carry template variables, so a per-job value goes straight into the path:

https://api.example.com/lines/{{input.line_id}}/test

Headers are pairs you add one at a time. This is where a credential goes.

Body Template appears for POST and PUT. It’s the request body, usually JSON, with template variables in it:

{
"line_id": "{{input.line_id}}",
"contact": "{{customer.email}}",
"reference": "{{job.job_number}}"
}

Store the credential in Secrets first. Then choose how the integration sends it.

Set an Authentication Type. Pick Bearer Token and give Secret Name the key of the secret, such as line_test_api_key. Each run sends Authorization: Bearer <the value>. Pick API Key instead and you also choose the Header Name the credential goes in, and an optional Header Prefix written before it. Leave the prefix empty and the header carries the credential on its own.

Or write the header yourself. Leave Authentication Type on No Auth and reference the secret by key in a header of your own:

Authorization: Bearer {{secrets.line_test_api_key}}

Both routes send the same request. Use the second one when the service wants the credential somewhere a single header cannot express, such as in the URL or the body.

Only a Tenant Admin can save an integration that references a secret. That holds for editing one too, so an integration that already carries a credential needs a Tenant Admin to change its URL.

When a credential changes at the other end, open the secret and choose Rotate Value. The next run uses the new value and the integration itself doesn’t change, because it refers to the name. Deleting a secret is different: every integration referring to it starts failing with a message saying the secret wasn’t found, so point them elsewhere first.

If the credential isn’t working:

  • The other service returns 401 or 403. Open the run in the execution log and check the request went where you expected, then check the credential at the other end. A key rotated at their side needs rotating at yours.
  • The run fails before any request goes out, naming the destination. The secret is pinned, and the integration’s hostname isn’t on its Allowed destinations list. Add it, or clear the list to unpin the secret.
  • The run fails saying the secret wasn’t found. The key inside {{secrets.…}} doesn’t match a secret you hold. Names are exact.

Anywhere you can type, {{namespace.key}} is replaced when the request is made.

Variable What it holds
{{input.your_key}} A value from the integration’s own input parameters
{{secrets.your_key}} A credential from your secrets
{{job.job_number}} Fields from the job the run belongs to
{{customer.email}} Fields from that job’s customer
{{callback_url}} The address an async integration must post its result back to

Personal details are kept out of the URL and the headers, because those end up in the other service’s logs. In a URL or a header you can use job.id, job.job_number, job.status, customer.id and customer.external_crm_id. Everything else about the job or the customer goes in the body template, where the full set is available. Use one of the restricted ones in a URL and the run fails with a message naming the variable, so you’ll find out on your first test.

A value that would break out of the URL or header it sits in is rejected rather than sent. If a run fails with a variable error, look at what the field actually contained.

Sync sends the request and waits for the answer. The result comes back within seconds and appears straight away on the job form. Use it for lookups, checks and validations.

Async sends the request and stops waiting. The other service does the work in its own time and posts the result back to WorkWing when it’s ready. The person on the job sees a pending state and the answer fills in when it arrives. Use it for anything slow: a line test, provisioning, a job queued at the other end.

An async integration has to be told where to send the result. Put {{callback_url}} in the body template:

{
"line_id": "{{input.line_id}}",
"callback": "{{callback_url}}"
}

WorkWing builds that address fresh for every run and it carries a token that only that one run can use. The other service posts its result there with an ordinary HTTP POST. A callback without the token, or one for a run that has already finished or timed out, is turned away.

Input parameters are the values the integration needs each time it runs. Each one has a Label, a Key in lowercase with underscores, a Type of Text, Number or Boolean, an optional Default Value, a Req tick for whether it’s required, and a description that shows as help text. Use the key as {{input.your_key}} in the URL or body.

Output parameters are the pieces of the reply you want to keep. Each has a Label, a Key, a Path into the response such as data.status, and a Type. These are what you map onto form fields when you put the integration on a job type, so declare one for every value the person on site needs to see.

Most APIs wrap the useful part of their answer in something. The Transform Expression reshapes the reply before the output paths are read.

Run a test first, then use the response tree below the field. Click a node and choose Set transform target to make it the expression, or Map to output to turn it into an output parameter. Returns preview shows what your current expression produces against the last response. Expression mode lets you type one by hand.

The expression language is JSONata, so it can do more than pick a node out:

results.voltage > 220 ? "PASS" : "FAIL"

Without an expression, output paths read the reply as it arrived. With one, they read whatever the expression returns. If the reply is { "data": { "results": { "status": "PASS" } } } and the expression is data.results, then an output parameter with the path status gets PASS.

If the same inputs always produce the same answer, caching saves the round trip.

Turn on Enable Caching and set Cache TTL (seconds), the length of time a stored answer stays good. Zero means it never expires on its own.

Cache key fields decides what counts as “the same inputs”. Leave it empty and every input parameter is used. Name specific keys when only some of them change the answer.

Clear Cache throws away everything stored for this integration. The next run goes out to the other service whatever the TTL says. Max response size below it is for information; it’s the largest reply WorkWing will accept.

The row menu offers two different things. Deactivate switches the integration off and leaves everything in place; switch it back on whenever you like. Delete removes the integration permanently, along with its whole execution history, and cannot be undone. You’ll be asked to type the integration’s name to confirm.

Either way, any job form field pointing at the integration stops being able to fetch anything, so check the form builder before you do.

A delete is refused while the integration still has work queued or running, so a run in progress is never cut off half-way. Wait for the executions to finish, then delete. Deactivating first stops new work being queued.