05 May 2026 11:41 AM
Hi team,
I am trying to create a synthetic monitor that obtains a token via JavaScript and then makes an API call using that token as the Authorization token. After the API call, I want to send the result in an email.
What is the best way to do this? Where can we store the token to use it later on a workflow?
We need a way to store the tokento use it because the result of a batch execution doesn't show the token.
Thanks in advance!
Elena
05 May 2026 12:16 PM
Hello, @elenaperez!
Could you share why you need to fetch the token from the Synthetic monitor with JavaScript?
Is the token only created after a browser login, or is it just stored in localStorage by the application?
Also, why not use Credential Vault instead? The usual pattern is to store the authentication secrets there, get the token directly in the Workflow, use it in the next API call, and then send the email. This is safer than trying to pass the token from Synthetic execution results.
I hope it helps you 😁
05 May 2026 12:26 PM
The token is stored on the localstorage after doing some steps on the page. That is the only way to get it.
These are the steps before obtaining the token:
If I store the token in the credential vault, which option should I select?
And how do I get it from the workflow later?
05 May 2026 12:35 PM
Thanks for sharing the steps.
Since the token is only created after the browser login and stored in localStorage, it makes sense that you are retrieving it with JavaScript inside the Synthetic monitor.
However, I would avoid storing that generated token in Credential Vault as I said. Credential Vault is intended for static credentials, such as username/password, client ID, client secret, or API keys, not for short-lived session tokens generated during a browser session.
If the API call depends on that localStorage token, the cleanest option is to perform the API call in the same Synthetic JavaScript step, immediately after reading the token.
If the final goal is to send the API result by email through a Workflow, then the better long-term design would be to move the authentication/API logic into the Workflow, using Credential Vault only for the login credentials or client credentials. The Workflow would then generate the token, call the API, format the result, and send the email.
For the Credential Vault scope:
I would not store the runtime token itself in Credential Vault unless there is no other option and you are aware of the expiration and security implications.
05 May 2026 12:40 PM
I am not sure I understand. If I do the API call from the workflow, how do I obtain the token generated from the synthetic?
05 May 2026 12:45 PM
You would not obtain the token generated by the Synthetic monitor from the Workflow. That is the key point.
The token created during the Synthetic execution belongs to that browser session runtime, and it is not meant to be passed later to a Workflow. Synthetic results also may hide sensitive values, so it is not a reliable place to extract a token from.
If the API call is done from the Workflow, then the Workflow should also obtain its own token. In that design, Credential Vault stores the static credentials, such as username/password, client ID, or client secret. Then the Workflow uses those credentials to authenticate, receives a new token, uses it in the API call, and sends the email.
So the desing would be something like this:
Workflow 1. Read credentials from Credential Vault 2. Authenticate and get a new token 3. Call the API using that token 4. Format the response 5. Send the email
If the token can only be created by going through the browser flow in Synthetic and there is no backend authentication endpoint available, then the API call should stay inside the Synthetic JavaScript step, right after reading the token from Localstorage. In that case, the Workflow will not be able to reuse that Synthetic-generated token directly.
05 May 2026 02:05 PM
The only way to obtain the token is via the browser flow. What about sending the API result on a log?
Something is not working with my code:
(async function() {
// 1) Leer token RINA
var rinaToken = localStorage.getItem("rina_token") || "";
if (!rinaToken) {
api.error("[RINA] Token not found");
return;
}
// 2) Llamada a la API de RINA
var RINA_API_URL = "https://{environmentid}.live.dynatrace.com/api/v2/logs/ingest";
var DT_LOG_INGEST_TOKEN = "****";
try {
var logPayload = [{
content: rinaResponseBody,
attributes: {
source: "synthetic-rina",
api: "PendingMessages",
synthetic: "browser-clickpath",
environment: "prod"
},
timestamp: Date.now()
}];
var dtResp = await fetch(DT_LOG_INGEST_URL, {
method: "POST",
headers: {
"Authorization": "Api-Token " + DT_LOG_INGEST_TOKEN,
"Content-Type": "application/json; charset=utf-8"
},
body: JSON.stringify(logPayload)
});
api.info("[DT_LOG_INGEST] status=" + dtResp.status);
} catch (e) {
api.error("[DT_LOG_INGEST] error=" + (e && e.message ? e.message : String(e)));
}
})();I don't see any logs ingested with that format
05 May 2026 02:15 PM
Yeah, could work as a workaround.
The main problem is that the script defines this variable:
var RINA_API_URL = "https://{environmentid}.live.dynatrace.com/api/v2/logs/ingest";but later it uses:
fetch(DT_LOG_INGEST_URL, ...)DT_LOG_INGEST_URL is not defined, so the request will fail before anything is ingested.
There is also another issue: rinaResponseBody is used in the log payload, but it is not defined in the snippet. You first need to make the RINA API call with the token, read the response body, and only then send that result to Dynatrace Logs.
05 May 2026 02:36 PM
After fixing, I get this error:
05 May 2026 02:38 PM
Can I see the StackTrace complete, please?
05 May 2026 02:50 PM
How is your JS code now, after you fixed?
05 May 2026 02:57 PM - edited 05 May 2026 03:01 PM
(async function() {
// 1) Leer token RINA
var rinaToken = localStorage.getItem("rina_token") || "";
if (!rinaToken) {
api.error("[RINA] Token not found");
return;
}
// 2) Llamada a la API de RINA
var RINA_API_URL = "https://rinaes01.portal.ss/eessiRest/PendingMessages?date=2026-05-05";
var rinaResponseBody = "";
try {
var rinaResp = await fetch(RINA_API_URL, {
method: "GET",
headers: {
"Authorization": "Bearer " + rinaToken,
"Content-Type": "application/json"
}
});
rinaResponseBody = await rinaResp.text();
api.info("[RINA] API status=" + rinaResp.status);
} catch (e) {
api.error("[RINA] API error=" + (e && e.message ? e.message : String(e)));
return;
}
05 May 2026 03:02 PM
// 3) Ingestar el RESULTADO de la API en Dynatrace Logs
var DT_LOG_INGEST_URL = "https://{environmentid}.live.dynatrace.com/api/v2/logs/ingest";
var DT_LOG_INGEST_TOKEN = "***";
try {
var logPayload = [{
content: rinaResponseBody,
attributes: {
source: "synthetic-rina",
api: "PendingMessages",
synthetic: "browser-clickpath",
environment: "prod"
},
timestamp: Date.now()
}];
var dtResp = await fetch(DT_LOG_INGEST_URL, {
method: "POST",
headers: {
"Authorization": "Api-Token " + DT_LOG_INGEST_TOKEN,
"Content-Type": "application/json; charset=utf-8"
},
body: JSON.stringify(logPayload)
});
api.info("[DT_LOG_INGEST] status=" + dtResp.status);
} catch (e) {
api.error("[DT_LOG_INGEST] error=" + (e && e.message ? e.message : String(e)));
}
})();
05 May 2026 03:07 PM
The code looks close, but I would check the exact error returned by the Synthetic execution first. Is it Failed to fetch, 401, 403, 400, or only the stack frame with Object.fail?
Since this runs inside a Synthetic browser clickpath, the call to the Dynatrce Log Ingest API is executed from the browser context. This means it can be blocked by CORS or browser security restrictions, even if the token and payload are correct.
Can you also log the response body from the Log Ingest call?
05 May 2026 03:21 PM
Where can I see the response logged with api.info("[DT_LOG_INGEST] response body=" + dtRespBody); ?
05 May 2026 03:23 PM
In the Synthetic monitor execution details. Open the Synthetic monitor, then go to the latest execution result or failed execution. In the execution details, open the specific step where the custom JavaScript runs. The messages written with api.info() should appear in the step logs/details for that execution.
If nothing appears, it may mean the script failed before reaching that line, or that the fetch() call failed before a response was returned. In that case, add an api.info() right before the fetch() and keep the catch block with api.error() to confirm where it stops.
05 May 2026 03:33 PM
I added the api.info() before the fetch and don't see anything
05 May 2026 03:54 PM
Try using api.startAsyncSyntheticEvent() at the beginning of the JavaScript step and api.finish() at the end of every success/error path. Here is the doc
05 May 2026 03:55 PM
Like this code:
api.startAsyncSyntheticEvent();
(async function () {
try {
var rinaToken = localStorage.getItem("rina_token") || "";
if (!rinaToken) {
api.error("[RINA] Token not found");
api.finish();
return;
}
var RINA_API_URL = "https://{environmentid}.live.dynatrace.com/api/v2/logs/ingest";
var DT_LOG_INGEST_TOKEN = "***";
var logPayload = [{
content: rinaResponseBody,
loglevel: "INFO",
"log.source": "synthetic-rina",
"rina.api": "PendingMessages",
"synthetic.type": "browser-clickpath",
environment: "prod",
"rina.status_code": String(rinaResp.status),
timestamp: Date.now()
}];
var dtResp = await fetch(DT_LOG_INGEST_URL, {
method: "POST",
headers: {
"Authorization": "Api-Token " + DT_LOG_INGEST_TOKEN,
"Content-Type": "application/json; charset=utf-8"
},
body: JSON.stringify(logPayload)
});
var dtRespBody = await dtResp.text();
api.info("[DT_LOG_INGEST] status=" + dtResp.status + " body=" + dtRespBody);
api.finish();
} catch (e) {
api.error("[SCRIPT] error=" + (e && e.message ? e.message : String(e)));
api.finish();
}
})();
Featured Posts