Issue Summary: Validation Failure on All Webhook Events Using Lambda
I’m experiencing consistent issues validating webhook requests from Snipcart across all events (using order.completed
as an example). I’ve followed the documentation and examples, testing multiple approaches, but validation hasn’t succeeded.
Setup Details:
- I implemented an AWS Lambda function in JavaScript, exposed via an API Gateway, and set this URL as the webhook endpoint.
- I successfully retrieve the
X-Snipcart-RequestToken
from each event and include theAPI_SECRET
key in the validation request.
Approaches and Code Variants Tried: I tested passing the API_SECRET
in both clear text and as a base64-encoded string in each of two code variants. Here’s a sample of each:
- Clear Text and Encoded API Key in the
auth
Field:
javascript
Copy code
async function validateRequestToken(token, apiSecret) {
try {
const encodedKey = Buffer.from(`${apiSecret}:`).toString('base64'); // Encode API key with trailing colon
const url = `https://app.snipcart.com/api/requestvalidation/${token}`;
console.log("Validating token:", token);
console.log("Encoded API Key:", encodedKey);
const response = await axios.get(url, {
auth: {
username: encodedKey, // Attempted as both encoded and clear text
password: '',
},
});
console.log("Received response:", response);
return response;
} catch (error) {
console.error('Token validation error:', error);
return false;
}
}
- Clear Text and Encoded API Key in the
Authorization
Header:
javascript
Copy code
async function validateRequestToken(token, apiSecret) {
try {
console.log("Validating token:", token);
console.log("Using API Secret:", apiSecret);
const response = await axios.get(`https://app.snipcart.com/api/requestvalidation/${token}`, {
headers: {
Authorization: `Basic ${Buffer.from(apiSecret + ':').toString('base64')}`, // Tried both encoded and clear text
},
});
console.log("Received response:", response);
return response.data.ok;
} catch (error) {
console.error('Token validation error:', error);
return false;
}
}
Current Outcome:
- Neither approach (clear text or encoded key) has yielded a successful validation; errors or no valid response are consistently returned.
- Webhook events work in test mode without validation but fail when validation is enabled.
I would appreciate any insights into the cause of these issues, particularly with respect to encoding, header structure, or any specific requirements that might be missing. Thank you for your assistance.
here is sample event error from CloudWatch ( when Secretkey is encoded)
2024-10-27T15:16:32.351Z 3a98fd42-0519-475e-b141-168a761c7489 ERROR Token validation error: AxiosError: Request failed with status code 401
at settle (/var/task/node_modules/axios/dist/node/axios.cjs:2019:12)
at IncomingMessage.handleStreamEnd (/var/task/node_modules/axios/dist/node/axios.cjs:3135:11)
at IncomingMessage.emit (node:events:529:35)
at IncomingMessage.emit (node:domain:489:12)
at endReadableNT (node:internal/streams/readable:1400:12)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
at Axios.request (/var/task/node_modules/axios/dist/node/axios.cjs:4287:41)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async validateRequestToken (/var/task/index.js:48:26)
at async validateEvent (/var/task/index.js:31:12)
at async exports.handler (/var/task/index.js:191:15) {
code: ‘ERR_BAD_REQUEST’,
config: {
transitional: {
silentJSONParsing: true,
forcedJSONParsing: true,
clarifyTimeoutError: false
},
adapter: [ ‘xhr’, ‘http’, ‘fetch’ ],
transformRequest: [ [Function: transformRequest] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 0,
xsrfCookieName: ‘XSRF-TOKEN’,
xsrfHeaderName: ‘X-XSRF-TOKEN’,
maxContentLength: -1,
maxBodyLength: -1,
env: { FormData: [Function], Blob: [class Blob] },
validateStatus: [Function: validateStatus],
headers: Object [AxiosHeaders] {
Accept: ‘application/json, text/plain, /’,
‘Content-Type’: undefined,
‘User-Agent’: ‘axios/1.7.7’,
‘Accept-Encoding’: ‘gzip, compress, deflate, br’
},
auth: {
username: ‘WXpOalpESTROalF0WVdObU1DMDBPV0ZoTFdFNU5UWXROamhsT1dZek5tRmlNMlU1TmpNNE5EZzVOVEUyTXpNMU5qVXdOall6Og==’,
password: ‘’
},
method: ‘get’,
url: ‘https://app.snipcart.com/api/requestvalidation/b1072856-50da-48e4-8eef-76c90ec89304’,
data: undefined
},
request: <ref *1> ClientRequest {
_events: [Object: null prototype] {
abort: [Function (anonymous)],
aborted: [Function (anonymous)],
connect: [Function (anonymous)],
error: [Function (anonymous)],
socket: [Function (anonymous)],
timeout: [Function (anonymous)],
finish: [Function: requestOnFinish]
},
_eventsCount: 7,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
strictContentLength: false,
_contentLength: 0,
_hasBody: true,
_trailer: ‘’,
finished: true,
_headerSent: true,
_closed: false,
socket: TLSSocket {
_tlsOptions: [Object],
_secureEstablished: true,
_securePending: false,
_newSessionPending: false,
_controlReleased: true,
secureConnecting: false,
_SNICallback: null,
servername: ‘app.snipcart.com’,
alpnProtocol: false,
authorized: true,
authorizationError: null,
encrypted: true,
_events: [Object: null prototype],
_eventsCount: 10,
connecting: false,
_hadError: false,
_parent: null,
_host: ‘app.snipcart.com’,
_closeAfterHandlingError: false,
_readableState: [ReadableState],
_maxListeners: undefined,
_writableState: [WritableState],
allowHalfOpen: false,
_sockname: null,
_pendingData: null,
_pendingEncoding: ‘’,
server: undefined,
_server: null,
ssl: [TLSWrap],
_requestCert: true,
_rejectUnauthorized: true,
parser: null,
_httpMessage: [Circular *1],
[Symbol(alpncallback)]: null,
[Symbol(res)]: [TLSWrap],
[Symbol(verified)]: true,
[Symbol(pendingSession)]: null,
[Symbol(async_id_symbol)]: 108,
[Symbol(kHandle)]: [TLSWrap],
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: null,
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kSetNoDelay)]: false,
[Symbol(kSetKeepAlive)]: true,
[Symbol(kSetKeepAliveInitialDelay)]: 60,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0,
[Symbol(connect-options)]: [Object]
},
_header: ‘GET /api/requestvalidation/b1072856-50da-48e4-8eef-76c90ec89304 HTTP/1.1\r\n’ +
‘Accept: application/json, text/plain, /\r\n’ +
‘User-Agent: axios/1.7.7\r\n’ +
‘Accept-Encoding: gzip, compress, deflate, br\r\n’ +
‘Host: app.snipcart.com\r\n’ +
‘Authorization: Basic V1hwT2FscEVTVFJPYWxGMFdWZE9iVTFETURCUFYwWm9URmRGTlU1VVdYUk9hbWhzVDFkWmVrNXRSbWxOTWxVMVRtcE5ORTVFWnpWT1ZFVXlUWHBOTVU1cVZYZE9hbGw2T2c9PTo=\r\n’ +
‘Connection: close\r\n’ +
‘\r\n’,
_keepAliveTimeout: 0,
_onPendingData: [Function: nop],
agent: Agent {
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
defaultPort: 443,
protocol: ‘https:’,
options: [Object: null prototype],
requests: [Object: null prototype] {},
sockets: [Object: null prototype],
freeSockets: [Object: null prototype] {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256,
scheduling: ‘lifo’,
maxTotalSockets: Infinity,
totalSocketCount: 1,
maxCachedSessions: 100,
_sessionCache: [Object],
[Symbol(kCapture)]: false
},
socketPath: undefined,
method: ‘GET’,
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
joinDuplicateHeaders: undefined,
path: ‘/api/requestvalidation/b1072856-50da-48e4-8eef-76c90ec89304’,
_ended: true,
res: IncomingMessage {
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 4,
_maxListeners: undefined,
socket: [TLSSocket],
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: ‘1.1’,
complete: true,
rawHeaders: [Array],
rawTrailers: [],
joinDuplicateHeaders: undefined,
aborted: false,
upgrade: false,
url: ‘’,
method: null,
statusCode: 401,
statusMessage: ‘Unauthorized’,
client: [TLSSocket],
_consuming: false,
_dumped: false,
req: [Circular *1],
responseUrl: ‘https://WXpOalpESTROalF0WVdObU1DMDBPV0ZoTFdFNU5UWXROamhsT1dZek5tRmlNMlU1TmpNNE5EZzVOVEUyTXpNMU5qVXdOall6Og%3D%3D:@app.snipcart.com/api/requestvalidation/b1072856-50da-48e4-8eef-76c90ec89304’,
redirects: [],
[Symbol(kCapture)]: false,
[Symbol(kHeaders)]: [Object],
[Symbol(kHeadersCount)]: 16,
[Symbol(kTrailers)]: null,
[Symbol(kTrailersCount)]: 0
},
aborted: false,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
reusedSocket: false,
host: ‘app.snipcart.com’,
protocol: ‘https:’,
_redirectable: Writable {
_writableState: [WritableState],
_events: [Object: null prototype],
_eventsCount: 3,
_maxListeners: undefined,
_options: [Object],
_ended: true,
_ending: true,
_redirectCount: 0,
_redirects: [],
_requestBodyLength: 0,
_requestBodyBuffers: [],
_onNativeResponse: [Function (anonymous)],
_currentRequest: [Circular *1],
_currentUrl: ‘https://WXpOalpESTROalF0WVdObU1DMDBPV0ZoTFdFNU5UWXROamhsT1dZek5tRmlNMlU1TmpNNE5EZzVOVEUyTXpNMU5qVXdOall6Og%3D%3D:@app.snipcart.com/api/requestvalidation/b1072856-50da-48e4-8eef-76c90ec89304’,
[Symbol(kCapture)]: false
},
[Symbol(kCapture)]: false,
[Symbol(kBytesWritten)]: 0,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype] {
accept: [Array],
‘user-agent’: [Array],
‘accept-encoding’: [Array],
host: [Array],
authorization: [Array]
},
[Symbol(errored)]: null,
[Symbol(kHighWaterMark)]: 16384,
[Symbol(kRejectNonStandardBodyWrites)]: false,
[Symbol(kUniqueHeaders)]: null
},
response: {
status: 401,
statusText: ‘Unauthorized’,
headers: Object [AxiosHeaders] {
‘content-length’: ‘0’,
connection: ‘close’,
date: ‘Sun, 27 Oct 2024 15:16:31 GMT’,
‘access-control-expose-headers’: ‘Request-Context’,
‘cache-control’: ‘no-cache’,
expires: ‘-1’,
pragma: ‘no-cache’,
‘request-context’: ‘appId=cid-v1:48f65c7b-986c-4ec3-89de-eb0160f55639’
},
config: {
transitional: [Object],
adapter: [Array],
transformRequest: [Array],
transformResponse: [Array],
timeout: 0,
xsrfCookieName: ‘XSRF-TOKEN’,
xsrfHeaderName: ‘X-XSRF-TOKEN’,
maxContentLength: -1,
maxBodyLength: -1,
env: [Object],
validateStatus: [Function: validateStatus],
headers: [Object [AxiosHeaders]],
auth: [Object],
method: ‘get’,
url: ‘https://app.snipcart.com/api/requestvalidation/b1072856-50da-48e4-8eef-76c90ec89304’,
data: undefined
},
request: <ref *1> ClientRequest {
_events: [Object: null prototype],
_eventsCount: 7,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
strictContentLength: false,
_contentLength: 0,
_hasBody: true,
_trailer: ‘’,
finished: true,
_headerSent: true,
_closed: false,
socket: [TLSSocket],
_header: ‘GET /api/requestvalidation/b1072856-50da-48e4-8eef-76c90ec89304 HTTP/1.1\r\n’ +
‘Accept: application/json, text/plain, /\r\n’ +
‘User-Agent: axios/1.7.7\r\n’ +
‘Accept-Encoding: gzip, compress, deflate, br\r\n’ +
‘Host: app.snipcart.com\r\n’ +
‘Authorization: Basic V1hwT2FscEVTVFJPYWxGMFdWZE9iVTFETURCUFYwWm9URmRGTlU1VVdYUk9hbWhzVDFkWmVrNXRSbWxOTWxVMVRtcE5ORTVFWnpWT1ZFVXlUWHBOTVU1cVZYZE9hbGw2T2c9PTo=\r\n’ +
‘Connection: close\r\n’ +
‘\r\n’,
_keepAliveTimeout: 0,
_onPendingData: [Function: nop],
agent: [Agent],
socketPath: undefined,
method: ‘GET’,
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
joinDuplicateHeaders: undefined,
path: ‘/api/requestvalidation/b1072856-50da-48e4-8eef-76c90ec89304’,
_ended: true,
res: [IncomingMessage],
aborted: false,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
reusedSocket: false,
host: ‘app.snipcart.com’,
protocol: ‘https:’,
_redirectable: [Writable],
[Symbol(kCapture)]: false,
[Symbol(kBytesWritten)]: 0,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype],
[Symbol(errored)]: null,
[Symbol(kHighWaterMark)]: 16384,
[Symbol(kRejectNonStandardBodyWrites)]: false,
[Symbol(kUniqueHeaders)]: null
},
data: ‘’
},
status: 401
}