How do I construct this url rewrite policy in Azure APIM? - azure

In Azure APIM, looking for a url rewrite policy (I think), that looks for a certain string and constructs the backend url based on that string.
Example1:
Converts this
https://myfrontend.com/locations/usa/remaining-url
to this
https://usa.com/remaining-url
Example1:
Converts this
https://myfrontend.com/locations/canada/remaining-url
to this
https://canada.com/remaining-url

It depends on your API Management API / Operation.
In my example I designed it this way:
'/locations/{locationid}/*':
get:
summary: Locations with Id
operationId: locations-with-id
parameters:
- name: locationid
in: path
required: true
schema:
enum:
- usa
- canada
type: string
There's a matchedparameter locationid which will be used in the policy.
The path ends with a wildcard /* to support the remaining URL.
For getting the remaining URL, the following Regex is used:
\/locations\/[^\/]*(\/[a-zA-Z0-9-?=#_]*)
API with operation GET /locations/{locationid}/* :
Operation policy:
<policies>
<inbound>
<base />
<set-variable name="RegexLocation" value="\/locations\/[^\/]*(\/[a-zA-Z0-9-?=#_]*)" />
<set-variable name="remainingUrl" value="#{
string pattern = context.Variables.GetValueOrDefault<string>("RegexLocation");
var regex = new Regex(pattern);
var match = regex.Match(context.Request.OriginalUrl.Path.ToString());
if (match.Success)
{
return match.Groups[1].Value;;
}
else
{
return string.Empty;
}
}" />
<choose>
<when condition="#(context.Request.MatchedParameters.GetValueOrDefault("locationid", "") == "usa")">
<set-backend-service base-url="https://usa.com" />
</when>
<when condition="#(context.Request.MatchedParameters.GetValueOrDefault("locationid", "") == "canada")">
<set-backend-service base-url="https://canada.com" />
</when>
</choose>
<rewrite-uri template="#(context.Variables.GetValueOrDefault<string>("remainingUrl"))" copy-unmatched-params="true" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
The choose policy is used to specify the backend.
The policy rewrite-uri adds the renaming path and query
Output from trace for request:
GET https://rfqapiservicey27itmeb4cf7q.azure-api.net/abc/locations/usa/lorem?ipsum=1
The new backend URL is:
https://usa.com/lorem?ipsum=1
{
"traceEntries": {
"inbound": [
{
"source": "set-variable",
"timestamp": "2022-08-13T07:08:45.5496188Z",
"elapsed": "00:00:00.0003989",
"data": {
"message": "Context variable was successfully set.",
"name": "RegexLocation",
"value": "\\/locations\\/[^\\/]*(\\/[a-zA-Z0-9-?=#_]*)"
}
},
{
"source": "set-variable",
"timestamp": "2022-08-13T07:08:45.5496188Z",
"elapsed": "00:00:00.0004445",
"data": {
"message": "Expression was successfully evaluated.",
"expression": "\n string pattern = context.Variables.GetValueOrDefault<string>(\"RegexLocation\");\n\n var regex = new Regex(pattern);\n var match = regex.Match(context.Request.OriginalUrl.Path.ToString());\n\n if (match.Success)\n { \n return match.Groups[1].Value;;\n } \n else\n {\n return string.Empty;\n }\n\n ",
"value": "/lorem"
}
},
{
"source": "set-variable",
"timestamp": "2022-08-13T07:08:45.5496188Z",
"elapsed": "00:00:00.0004465",
"data": {
"message": "Context variable was successfully set.",
"name": "remainingUrl",
"value": "/lorem"
}
},
{
"source": "choose",
"timestamp": "2022-08-13T07:08:45.5496188Z",
"elapsed": "00:00:00.0004581",
"data": {
"message": "Expression was successfully evaluated.",
"expression": "context.Request.MatchedParameters.GetValueOrDefault(\"locationid\", \"\") == \"usa\"",
"value": true
}
},
{
"source": "set-backend-service",
"timestamp": "2022-08-13T07:08:45.5496188Z",
"elapsed": "00:00:00.0004690",
"data": {
"message": "Backend service URL was changed.",
"oldBackendServiceUrl": "https://rfqapiservicey27itmeb4cf7q.azure-api.net/abc",
"newBackendServiceUrl": "https://usa.com/",
"request": { "url": "https://usa.com/locations/usa/lorem?ipsum=1" }
}
},
{
"source": "rewrite-uri",
"timestamp": "2022-08-13T07:08:45.5496188Z",
"elapsed": "00:00:00.0004757",
"data": {
"message": "Expression was successfully evaluated.",
"expression": "context.Variables.GetValueOrDefault<string>(\"remainingUrl\")",
"value": "/lorem"
}
},
{
"source": "rewrite-uri",
"timestamp": "2022-08-13T07:08:45.5496188Z",
"elapsed": "00:00:00.0005407",
"data": {
"message": "Updated request URL per specified rewrite template.",
"request": { "url": "https://usa.com/lorem?ipsum=1" }
}
}
],
"backend": [
{
"source": "forward-request",
"timestamp": "2022-08-13T07:08:45.5496188Z",
"elapsed": "00:00:00.0007386",
"data": {
"message": "Request is being forwarded to the backend service. Timeout set to 300 seconds",
"request": {
"method": "GET",
"url": "https://usa.com/lorem?ipsum=1"
}
}
}
]
}
}

Related

Azure API Management not getting Client Certificate for Multual TLS

I'm trying to verify Client Certificates in Azure API Management. I created a new instance and I'm using the default Echo API.
I followed this documentation https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-mutual-certificates-for-clients
and this one for testing with Postman https://medium.com/#jkewley/testing-client-certificate-authentication-to-azure-api-management-with-postman-e1cfae52fc35
I'm using the following Policy in the Echo API All operations Inbound just checking if any certificate is present:
<policies>
<inbound>
<choose>
<when condition="#(context.Request.Certificate == null)">
<return-response>
<set-status code="403" reason="Missing client certificate" />
</return-response>
</when>
</choose>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
In the Custom Domains tab, I have my Endpoint Gateway with Negotiate client certificate and Default SSL binding enabled.
When testing without the policy it works fine. With the policy, I get "403 - Missing client certificate".
My PostMan logs show my local pfx file being sent. I have used that same CA certificate successfully with an Apigee setup that I'm trying to replicate.
Postman Console
The APIM Trace shows no sign of that certificate
{
"traceId": "1e2950a4-7ae9-4489-9175-dd6b7a8e6872",
"traceEntries": {
"inbound": [
{
"source": "api-inspector",
"timestamp": "2021-03-08T16:45:36.1300291Z",
"elapsed": "00:00:00.0002376",
"data": {
"request": {
"method": "POST",
"url": "https://xxxxxx-poc-apim.azure-api.net/echo/resource",
"headers": [
{
"name": "Ocp-Apim-Subscription-Key",
"value": "20c7x7x22xa5xdxc8a1x857bb651000a"
},
{
"name": "X-Forwarded-For",
"value": "76.98.XX.XXX"
},
{
"name": "Connection",
"value": "keep-alive"
},
{
"name": "Content-Length",
"value": "102"
},
{
"name": "Content-Type",
"value": "text/plain"
},
{
"name": "Accept",
"value": "*/*"
},
{
"name": "Accept-Encoding",
"value": "gzip,deflate,br"
},
{
"name": "Host",
"value": "xxxxxxx-poc-apim.azure-api.net"
},
{
"name": "User-Agent",
"value": "PostmanRuntime/7.26.10"
}
]
}
}
},
{
"source": "api-inspector",
"timestamp": "2021-03-08T16:45:36.1300291Z",
"elapsed": "00:00:00.0002401",
"data": {
"configuration": {
"api": {
"from": "/echo",
"to": {
"scheme": "http",
"host": "echoapi.cloudapp.net",
"port": 80,
"path": "/api",
"queryString": "",
"query": {
},
"isDefaultPort": true
},
"version": null,
"revision": "1"
},
"operation": {
"method": "POST",
"uriTemplate": "/resource"
},
"user": "-",
"product": "-"
}
}
},
{
"source": "cors",
"timestamp": "2021-03-08T16:45:36.1300291Z",
"elapsed": "00:00:00.0002602",
"data": "Origin header was missing or empty and the request was classified as not cross-domain. CORS policy was not applied."
},
{
"source": "choose",
"timestamp": "2021-03-08T16:45:36.1300291Z",
"elapsed": "00:00:00.0002753",
"data": {
"message": "Expression was successfully evaluated.",
"expression": "context.Request.Certificate == null",
"value": true
}
},
{
"source": "set-status",
"timestamp": "2021-03-08T16:45:36.1300291Z",
"elapsed": "00:00:00.0002817",
"data": {
"message": [
"Response status code was set to 403",
"Response status reason was set to 'Missing client certificate'"
]
}
},
{
"source": "return-response",
"timestamp": "2021-03-08T16:45:36.1300291Z",
"elapsed": "00:00:00.0002863",
"data": {
"message": "Return response was applied",
"response": {
"status": {
"code": "Forbidden",
"reason": "Missing client certificate"
},
"headers": [
]
}
}
}
],
"outbound": [
{
"source": "transfer-response",
"timestamp": "2021-03-08T16:45:36.1300291Z",
"elapsed": "00:00:00.0003120",
"data": {
"message": "Response headers have been sent to the caller."
}
}
]
}
}
I have tried a lot of things. I tried using SoapUI instead of Postman I try with another CA certificate.
I tried on another APIM that has a CA certificate but is behind an App Gateway. Always the same result.
I'm out of ideas.
I found the issue. My company is using Netskope for web traffic control and it was messing with the Certificates.
I discovered it by testing it from my home computer which was working fine.
When connecting to the APIM URL from my work laptop, my Web Browser was not showing the default .azure-api.net certificate but instead a certificate .goskope.com.
We added *.azure-api.net domain to bypass Netskope checks and it solved the issue.

Azure API Management returns "404 - resource not found" but endpoint testing works

Does anybody have any ideas on why PUT requests to APIM return 404 “Resource not found” but other operation types return HTTP 200?
I can use the test functionality in the APIM to call the PUT operation endpoints and I can check the console output on back-end web app and see the calls come through. However when using Postman or the a frontend web app we get the resource not found error message.
I'm really confused because, as mentioned, other verbs work fine. We generate the API endpoint definition from Swagger so it's the exact same method that's used to define the other endpoints.
Postman output:
{
"statusCode": 404,
"message": "Resource not found"
}
EDIT: endpoint config
{
"openapi": "3.0.1",
"info": {
"title": "Foo",
"description": "",
"version": "1.0"
},
"servers": [{
"url": "https://custom.domain.com"
}],
"paths": {
"/api/v{version}/Tasks/{taskId}/Complete": {
"put": {
"tags": ["Tasks"],
"summary": "/api/v{version}/Tasks/{taskId}/Complete - PUT",
"operationId": "put-api-v-version-tasks-taskid-complete",
"parameters": [{
"name": "taskId",
"in": "path",
"description": "Format - int64.",
"required": true,
"schema": {
"type": "integer"
}
}, {
"name": "version",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Foo.Tasks.TaskStatusRequest"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/Foo.Tasks.TaskStatusRequest"
}
},
"application/*+json": {
"schema": {
"$ref": "#/components/schemas/Foo.Tasks.TaskStatusRequest"
}
}
}
},
"responses": {
"200": {
"description": "Success"
},
"400": {
"description": "Bad Request",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/Microsoft.AspNetCore.Mvc.ProblemDetails"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/Microsoft.AspNetCore.Mvc.ProblemDetails"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/Microsoft.AspNetCore.Mvc.ProblemDetails"
}
}
}
}
}
}
}
}
}
The policies in APIM weren't set to allow PUT methods.
<policies>
<inbound>
<cors allow-credentials="true">
<allowed-origins>
<origin>https://URL</origin>
</allowed-origins>
<allowed-methods>
<method>GET</method>
<method>POST</method>
<method>OPTIONS</method>
<method>PUT</method>
</allowed-methods>
<allowed-headers>
<header>*</header>
</allowed-headers>
<expose-headers>
<header>*</header>
</expose-headers>
</cors>
</inbound>
<backend>
<forward-request />
</backend>
<outbound />
<on-error />
</policies>
Setting <method>PUT</method> has resolved this.
The Postman query was sending a POST request not a PUT request. When we changed Postman to the proper method we got a 405 - method not allowed. That made the issue obvious.

Sharepoint online GraphAPI create list with invalid request

I am trying to create a list with in a site using the graph API.
URL: https://graph.microsoft.com/v1.0/sites/{tenantsharepoint.com}:/sites/{siteName}:/lists
Request Body:
{
"displayName": "Books",
"columns": [
{
"name": "Author",
"text": { }
},
{
"name": "PageCount",
"number": { }
}
],
"list": {
"template": "genericList"
}
}
It was working fine but all of sudden it started giving me below exception:
{
"error": {
"code": "invalidRequest",
"message": "Provided identifier is malformed - site collection id is not valid",
"innerError": {
"date": "2020-07-31T05:28:46",
"request-id": "302c5ee3-3799-4a24-a2a3-185d7801f78a"
}
}
}
Any pointers leads would be appreciated.

Azure APIM Policy to failover primary backend URL to secondary in case of a time out

My requirement is fairly simple. I have an APIM endpoint that calls a backend HTTP service with a predefined timeout. If the request times out or returns an error other than 'Bad Request' (Status Code > 400), the call needs to be retried with a fail-over HTTP service until success or utmost 5 times. I am trying to come with an APIM policy to accomplish this. While the retry and fail-over works if the returned status code is above 400, it doesn't happen if the request simply times out. If the request simply times out, the control doesn't even enter the <retry> block. I am fairly certain the retry condition I put in is not being capable to capture the time out condition. Could someone help me figure out the APIM policy to be used to record timeouts?
Below is the APIM policy.
<policies>
<inbound>
<base />
<set-variable name="isFlag" value="true" />
</inbound>
<backend>
<choose>
<when condition="#(context.Variables.GetValueOrDefault<string>("isFlag") == "true")">
<set-backend-service base-url="<primary url>" />
<forward-request timeout="5" />
<retry condition="#(context.Response == null || context.Response.StatusCode > 400)" count="5" interval="1" first-fast-retry="true">
<set-backend-service base-url="<secondary url>" />
</retry>
</when>
</choose>
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Below is the excerpt from the APIM trace
Inbound
(3.014 ms)
api-inspector (0.403 ms)
{
"request": {
"method": "GET",
"url": "https://gpcshare-apim-gpc-dev-aes.azure-api.net/statcode/405?sleep=7000",
"headers": [
{
"name": "Ocp-Apim-Subscription-Key",
"value": "a02f7f4e18d54d84ba55d4f122548072"
},
{
"name": "Sec-Fetch-Site",
"value": "cross-site"
},
{
"name": "Sec-Fetch-Mode",
"value": "cors"
},
{
"name": "X-Forwarded-For",
"value": "103.111.183.78"
},
{
"name": "Cache-Control",
"value": "no-cache, no-store"
},
{
"name": "Via",
"value": "ICAP/1.0 bmg70302.ibosscloud.com (IBOSS/0.4.4 iboss ICAP service )"
},
{
"name": "Content-Type",
"value": "text/plain;charset=UTF-8"
},
{
"name": "Accept",
"value": "*/*"
},
{
"name": "Accept-Encoding",
"value": "gzip,deflate,br"
},
{
"name": "Accept-Language",
"value": "en-US,en;q=0.9"
},
{
"name": "Host",
"value": "gpcshare-apim-gpc-dev-aes.azure-api.net"
},
{
"name": "Referer",
"value": "https://apimanagement.hosting.portal.azure.net/apimanagement/Content/1.42.0.1/apimap//apimap-apis/index.html?clientOptimizations=undefined&l=en.en-us&trustedAuthority=https%3A%2F%2Fportal.azure.com&shellVersion=undefined"
}
]
}
}
api-inspector (0.004 ms)
{
"configuration": {
"api": {
"from": "/statcode",
"to": null,
"version": null,
"revision": "1"
},
"operation": {
"method": "GET",
"uriTemplate": "/{code}"
},
"user": "-",
"product": "-"
}
}
cors (2.601 ms)
"Origin header was missing or empty and the request was classified as not cross-domain. CORS policy was not applied."
cors (0.002 ms)
"Origin header was missing or empty and the request was classified as not cross-domain. CORS policy was not applied."
set-variable (0.003 ms)
{
"message": "Context variable was successfully set.",
"name": "isFlag",
"value": "true"
}
Backend
(4,991.587 ms)↑ Back to top
choose (0.017 ms)
{
"message": "Expression was successfully evaluated.",
"expression": "context.Variables.GetValueOrDefault<string>(\"isFlag\") == \"true\"",
"value": true
}
set-backend-service (0.007 ms)
{
"message": "Backend service URL was changed.",
"oldBackendServiceUrl": "",
"newBackendServiceUrl": "https://httpstat.us/",
"request": {
"url": "https://httpstat.us/405?sleep=7000"
}
}
forward-request (0.151 ms)
{
"message": "Request is being forwarded to the backend service. Timeout set to 5 seconds",
"request": {
"method": "GET",
"url": "https://httpstat.us/405?sleep=7000",
"headers": [
{
"name": "Host",
"value": "httpstat.us"
},
{
"name": "Request-Id",
"value": "|f595c50c66d7453c97a7dc14c9c1af9e.2b06f656cb6c4704_b907aebb."
},
{
"name": "Ocp-Apim-Subscription-Key",
"value": "a02f7f4e18d54d84ba55d4f122548072"
},
{
"name": "Sec-Fetch-Site",
"value": "cross-site"
},
{
"name": "Sec-Fetch-Mode",
"value": "cors"
},
{
"name": "X-Forwarded-For",
"value": "103.111.183.78,13.91.254.72"
},
{
"name": "Cache-Control",
"value": "no-cache, no-store"
},
{
"name": "Via",
"value": "ICAP/1.0 bmg70302.ibosscloud.com (IBOSS/0.4.4 iboss ICAP service )"
},
{
"name": "Content-Type",
"value": "text/plain;charset=UTF-8"
},
{
"name": "Accept",
"value": "*/*"
},
{
"name": "Accept-Encoding",
"value": "gzip,deflate,br"
},
{
"name": "Accept-Language",
"value": "en-US,en;q=0.9"
},
{
"name": "Referer",
"value": "https://apimanagement.hosting.portal.azure.net/apimanagement/Content/1.42.0.1/apimap//apimap-apis/index.html?clientOptimizations=undefined&l=en.en-us&trustedAuthority=https%3A%2F%2Fportal.azure.com&shellVersion=undefined"
}
]
}
}
forward-request (4,991.413 ms)
{
"messages": [
"Error occured while calling backend service.",
"Request to the backend service timed out"
]
}
Outbound
(0.183 ms)↑ Back to top
transfer-response (0.067 ms)
{
"message": "Response headers have been sent to the caller."
}
transfer-response (0.116 ms)
{
"message": "Response body streaming to the caller is complete."
}
You can to have your logic inside the retry policy. There is a policy snippet sample that you can refer to. Specifically this line.
Here is the same policy for reference
<!--
This policy routes calls to the closest of two backend services, and fails over to the secondary if an HTTP 404 is returned.
It assumes that the API Manager is deployed in 'East US' and 'West Europe'. Similarly the policy (as is) assumes two backend services, in the same regions, vis:
https://hello-eus.azurewebsites.net/ (for East US); and
https://hello-weu.azurewebsites.net/ (for West Europe)
If a failure (HTTP 404) is returned from the backend service, the policy will re-route the call to the fail-over region.
The policy uses cached values to track which service has returned an error in the last 10 seconds, to avoid routing new requests to a backend which will likely fail.
-->
<policies>
<inbound>
<base />
<set-backend-service base-url="https://hello-eus.azurewebsites.net/" />
<choose>
<when condition="#(context.Deployment.Region.Equals("east us", System.StringComparison.InvariantCultureIgnoreCase))">
<set-backend-service base-url="https://hello-eus.azurewebsites.net/" />
<cache-lookup-value key="#("eus-down")" variable-name="is-eus-down" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<bool>("is-eus-down"))">
<set-backend-service base-url="https://hello-weu.azurewebsites.net/" />
</when>
</choose>
</when>
<when condition="#(context.Deployment.Region.Equals("west europe", System.StringComparison.InvariantCultureIgnoreCase))">
<set-backend-service base-url="https://hello-weu.azurewebsites.net/" />
<cache-lookup-value key="#("weu-down")" variable-name="is-weu-down" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<bool>("is-weu-down"))">
<set-backend-service base-url="https://hello-eus.azurewebsites.net/" />
</when>
</choose>
</when>
</choose>
</inbound>
<backend>
<retry condition="#(context.Response.StatusCode == 404)" count="2" interval="1" max-interval="10" delta="1" first-fast-retry="true">
<choose>
<when condition="#(context.Response != null && (context.Response.StatusCode == 404))">
<choose>
<when condition="#(context.Request.Url.Host.Contains("hello-eus.azurewebsites.net"))">
<set-backend-service base-url="https://hello-weu.azurewebsites.net" />
<cache-store-value key="#("eus-down")" value="#(true)" duration="10" />
</when>
<otherwise>
<set-backend-service base-url="https://hello-eus.azurewebsites.net" />
<cache-store-value key="#("weu-down")" value="#(true)" duration="10" />
</otherwise>
</choose>
</when>
</choose>
<forward-request />
</retry>
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>

How to send to Amazon's Alexa Event Gateway?

I am trying to test sending an event to the Amazon's Event Gateway for my Alexa Smart Home skill using Postman but I keep receiving an 'invalid access token exception.' I have read the Amazon's documentation on this but apparently I am missing something.
When I enable my skill, my Smart Home Lambda receives the AcceptGrant.
{
"directive": {
"header": {
"namespace": "Alexa.Authorization",
"name": "AcceptGrant",
"messageId": "b2862179-bc56-4bb2-ac05-ce55c7a3e977",
"payloadVersion": "3"
},
"payload": {
"grant": {
"type": "OAuth2.AuthorizationCode",
"code": "ANSVjPzpTDBsdfoRSyrs"
},
"grantee": {
"type": "BearerToken",
"token": "Atza|IwEB..."
}
}
}
}
My lambda sends a POST to 'https://api.amazon.com/auth/o2/token' to receive the Access and Refresh tokens. It then stores those tokens. Next, my Lamdba responds with the following:
{
"event": {
"header": {
"namespace": "Alexa.Authorization",
"name": "AcceptGrant.Response",
"messageId": "b2862179-bc56-4bb2-ac05-ce55c7a3e977",
"payloadVersion": "3"
},
"payload": {}
}
}
I then get a message web page that I have successfully linked my skill - all is good.
Next, I try to send an event to Amazon's Alexa event gateway using the Postman app. I put the Access token (I also tried the Refresh token) in the header as a 'BearerToken' type and the in the 'scope' of the 'endpoint' object.
POST https://api.amazonalexa.com/v3/events?Content-Type=application/json&charset=UTF-8
with a header that specifies a Bearer Token (Access token received earlier) and a body that contains the following:
{
"event": {
"header": {
"messageId": "abc-123-def-456",
"namespace": "Alexa",
"name": "ChangeReport",
"payloadVersion": "3"
},
"endpoint": {
"scope": {
"type": "BearerToken",
"token": "<access token>"
},
"endpointId": "MySmartSwitch-001"
},
"payload": {
"change": {
"cause": {
"type": "RULE_TRIGGER"
},
"properties": [
{
"namespace": "Alexa.ModeController",
"name": "mode",
"value": "Backup",
"timeOfSample": "2020-01-02T09:30:00ZZ",
"uncertaintyInMilliseconds": 50
}
]
}
}
},
"context": {
"properties": [
{
"namespace": "Alexa.PowerController",
"name": "powerState",
"value": "ON",
"timeOfSample": "2020-01-02T09:30:00Z",
"uncertaintyInMilliseconds": 60000
},
{
"namespace": "Alexa.EndpointHealth",
"name": "connectivity",
"value": {
"value": "OK"
},
"timeOfSample": "2020-01-02T09:30:00Z",
"uncertaintyInMilliseconds": 0
}
]
}
}
The response received is '401 Unauthorized'
{
"header": {
"namespace": "System",
"name": "Exception",
"messageId": "95bd23c3-76e6-472b-9c6d-74d436e1eb61"
},
"payload": {
"code": "INVALID_ACCESS_TOKEN_EXCEPTION",
"description": "Access token is not valid."
}
}
I figured out the issue. I was mistakenly sending parameters: Content-Type=application/json and charset=UTF-8 as well including them in the header - my bad. You just need to include them in the header.

Resources