[ARCHIVED] How to retrieve JWT from Canvas Data Services' Live events signed payload via HTTPS
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi all,
I have a POST endpoint written in Express configured to receive incoming requests from the Canvas Data Live events. The documentation below says the JWT exists in the request body but it appears to be empty when I'm troubleshooting. Where should I be expecting to find the signed JWT token?
Any help will be appreciated! Thanks!
Example of full request:
{ readableState: ReadableState { objectMode: false, highWaterMark: 16384, buffer: BufferList { head: null, tail: null, length: 0 }, length: 0, pipes: null, pipesCount: 0, flowing: null, ended: false, endEmitted: false, reading: false, sync: true, needReadable: false, emittedReadable: false, readableListening: false, resumeScheduled: false, paused: true, emitClose: true, autoDestroy: false, destroyed: false, defaultEncoding: 'utf8', awaitDrain: 0, readingMore: true, decoder: null, encoding: null }, readable: true, _events: [Object: null prototype] { end: [Function: resetHeadersTimeoutOnReqEnd] }, _eventsCount: 1, _maxListeners: undefined, socket: Socket { connecting: false, _hadError: false, _parent: null, _host: null, _readableState: ReadableState { objectMode: false, highWaterMark: 16384, buffer: BufferList { head: null, tail: null, length: 0 }, length: 0, pipes: null, pipesCount: 0, flowing: true, ended: false, endEmitted: false, reading: true, sync: false, needReadable: true, emittedReadable: false, readableListening: false, resumeScheduled: false, paused: false, emitClose: false, autoDestroy: false, destroyed: false, defaultEncoding: 'utf8', awaitDrain: 0, readingMore: false, decoder: null, encoding: null }, readable: true, _events: [Object: null prototype] { end: [Array], timeout: [Function: socketOnTimeout], data: [Function: bound socketOnData], error: [Function: socketOnError], close: [Array], drain: [Function: bound socketOnDrain], resume: [Function: onSocketResume], pause: [Function: onSocketPause] }, _eventsCount: 8, _maxListeners: undefined, _writableState: WritableState { objectMode: false, highWaterMark: 16384, finalCalled: false, needDrain: false, ending: false, ended: false, finished: false, destroyed: false, decodeStrings: false, defaultEncoding: 'utf8', length: 0, writing: false, corked: 0, sync: true, bufferProcessing: false, onwrite: [Function: bound onwrite], writecb: null, writelen: 0, bufferedRequest: null, lastBufferedRequest: null, pendingcb: 0, prefinished: false, errorEmitted: false, emitClose: false, autoDestroy: false, bufferedRequestCount: 0, corkedRequestsFree: [Object] }, writable: true, allowHalfOpen: true, _sockname: null, _pendingData: null, _pendingEncoding: '', server: Server { _events: [Object: null prototype], _eventsCount: 2, _maxListeners: undefined, _connections: 1, _handle: [TCP], _usingWorkers: false, _workers: [], _unref: false, allowHalfOpen: true, pauseOnConnect: false, httpAllowHalfOpen: false, timeout: 120000, keepAliveTimeout: 5000, maxHeadersCount: null, headersTimeout: 40000, _connectionKey: '6::::8449', [Symbol(IncomingMessage)]: [Function: IncomingMessage], [Symbol(ServerResponse)]: [Function: ServerResponse], [Symbol(asyncId)]: 11 }, _server: Server { _events: [Object: null prototype], _eventsCount: 2, _maxListeners: undefined, _connections: 1, _handle: [TCP], _usingWorkers: false, _workers: [], _unref: false, allowHalfOpen: true, pauseOnConnect: false, httpAllowHalfOpen: false, timeout: 120000, keepAliveTimeout: 5000, maxHeadersCount: null, headersTimeout: 40000, _connectionKey: '6::::8449', [Symbol(IncomingMessage)]: [Function: IncomingMessage], [Symbol(ServerResponse)]: [Function: ServerResponse], [Symbol(asyncId)]: 11 }, timeout: 120000, parser: HTTPParser { '0': [Function: parserOnHeaders], '1': [Function: parserOnHeadersComplete], '2': [Function: parserOnBody], '3': [Function: parserOnMessageComplete], '4': [Function: bound onParserExecute], _headers: [], _url: '', socket: [Circular], incoming: [Circular], outgoing: null, maxHeaderPairs: 2000, _consumed: true, onIncoming: [Function: bound parserOnIncoming], parsingHeadersStart: 0 }, on: [Function: socketListenerWrap], addListener: [Function: socketListenerWrap], prependListener: [Function: socketListenerWrap], _paused: false, _httpMessage: ServerResponse { _events: [Object: null prototype], _eventsCount: 1, _maxListeners: undefined, outputData: [], outputSize: 0, writable: true, _last: false, chunkedEncoding: false, shouldKeepAlive: true, useChunkedEncodingByDefault: true, sendDate: true, _removedConnection: false, _removedContLen: false, _removedTE: false, _contentLength: null, _hasBody: true, _trailer: '', finished: false, _headerSent: false, socket: [Circular], connection: [Circular], _header: null, _onPendingData: [Function: bound updateOutgoingData], _sent100: false, _expect_continue: false, req: [Circular], locals: [Object: null prototype] {}, [Symbol(kNeedDrain)]: false, [Symbol(isCorked)]: false, [Symbol(kOutHeaders)]: [Object: null prototype] }, [Symbol(asyncId)]: 40, [Symbol(kHandle)]: TCP { reading: true, onconnection: null, _consumed: true, [Symbol(owner)]: [Circular] }, [Symbol(lastWriteQueueSize)]: 0, [Symbol(timeout)]: Timeout { _idleTimeout: 120000, _idlePrev: [TimersList], _idleNext: [TimersList], _idleStart: 12341, _onTimeout: [Function: bound ], _timerArgs: undefined, _repeat: null, _destroyed: false, [Symbol(refed)]: false, [Symbol(asyncId)]: 41, [Symbol(triggerId)]: 40 }, [Symbol(kBuffer)]: null, [Symbol(kBufferCb)]: null, [Symbol(kBufferGen)]: null, [Symbol(kBytesRead)]: 0, [Symbol(kBytesWritten)]: 0 }, connection: Socket { connecting: false, _hadError: false, _parent: null, _host: null, _readableState: ReadableState { objectMode: false, highWaterMark: 16384, buffer: BufferList { head: null, tail: null, length: 0 }, length: 0, pipes: null, pipesCount: 0, flowing: true, ended: false, endEmitted: false, reading: true, sync: false, needReadable: true, emittedReadable: false, readableListening: false, resumeScheduled: false, paused: false, emitClose: false, autoDestroy: false, destroyed: false, defaultEncoding: 'utf8', awaitDrain: 0, readingMore: false, decoder: null, encoding: null }, readable: true, _events: [Object: null prototype] { end: [Array], timeout: [Function: socketOnTimeout], data: [Function: bound socketOnData], error: [Function: socketOnError], close: [Array], drain: [Function: bound socketOnDrain], resume: [Function: onSocketResume], pause: [Function: onSocketPause] }, _eventsCount: 8, _maxListeners: undefined, _writableState: WritableState { objectMode: false, highWaterMark: 16384, finalCalled: false, needDrain: false, ending: false, ended: false, finished: false, destroyed: false, decodeStrings: false, defaultEncoding: 'utf8', length: 0, writing: false, corked: 0, sync: true, bufferProcessing: false, onwrite: [Function: bound onwrite], writecb: null, writelen: 0, bufferedRequest: null, lastBufferedRequest: null, pendingcb: 0, prefinished: false, errorEmitted: false, emitClose: false, autoDestroy: false, bufferedRequestCount: 0, corkedRequestsFree: [Object] }, writable: true, allowHalfOpen: true, _sockname: null, _pendingData: null, _pendingEncoding: '', server: Server { _events: [Object: null prototype], _eventsCount: 2, _maxListeners: undefined, _connections: 1, _handle: [TCP], _usingWorkers: false, _workers: [], _unref: false, allowHalfOpen: true, pauseOnConnect: false, httpAllowHalfOpen: false, timeout: 120000, keepAliveTimeout: 5000, maxHeadersCount: null, headersTimeout: 40000, _connectionKey: '6::::8449', [Symbol(IncomingMessage)]: [Function: IncomingMessage], [Symbol(ServerResponse)]: [Function: ServerResponse], [Symbol(asyncId)]: 11 }, _server: Server { _events: [Object: null prototype], _eventsCount: 2, _maxListeners: undefined, _connections: 1, _handle: [TCP], _usingWorkers: false, _workers: [], _unref: false, allowHalfOpen: true, pauseOnConnect: false, httpAllowHalfOpen: false, timeout: 120000, keepAliveTimeout: 5000, maxHeadersCount: null, headersTimeout: 40000, _connectionKey: '6::::8449', [Symbol(IncomingMessage)]: [Function: IncomingMessage], [Symbol(ServerResponse)]: [Function: ServerResponse], [Symbol(asyncId)]: 11 }, timeout: 120000, parser: HTTPParser { '0': [Function: parserOnHeaders], '1': [Function: parserOnHeadersComplete], '2': [Function: parserOnBody], '3': [Function: parserOnMessageComplete], '4': [Function: bound onParserExecute], _headers: [], _url: '', socket: [Circular], incoming: [Circular], outgoing: null, maxHeaderPairs: 2000, _consumed: true, onIncoming: [Function: bound parserOnIncoming], parsingHeadersStart: 0 }, on: [Function: socketListenerWrap], addListener: [Function: socketListenerWrap], prependListener: [Function: socketListenerWrap], _paused: false, _httpMessage: ServerResponse { _events: [Object: null prototype], _eventsCount: 1, _maxListeners: undefined, outputData: [], outputSize: 0, writable: true, _last: false, chunkedEncoding: false, shouldKeepAlive: true, useChunkedEncodingByDefault: true, sendDate: true, _removedConnection: false, _removedContLen: false, _removedTE: false, _contentLength: null, _hasBody: true, _trailer: '', finished: false, _headerSent: false, socket: [Circular], connection: [Circular], _header: null, _onPendingData: [Function: bound updateOutgoingData], _sent100: false, _expect_continue: false, req: [Circular], locals: [Object: null prototype] {}, [Symbol(kNeedDrain)]: false, [Symbol(isCorked)]: false, [Symbol(kOutHeaders)]: [Object: null prototype] }, [Symbol(asyncId)]: 40, [Symbol(kHandle)]: TCP { reading: true, onconnection: null, _consumed: true, [Symbol(owner)]: [Circular] }, [Symbol(lastWriteQueueSize)]: 0, [Symbol(timeout)]: Timeout { _idleTimeout: 120000, _idlePrev: [TimersList], _idleNext: [TimersList], _idleStart: 12341, _onTimeout: [Function: bound ], _timerArgs: undefined, _repeat: null, _destroyed: false, [Symbol(refed)]: false, [Symbol(asyncId)]: 41, [Symbol(triggerId)]: 40 }, [Symbol(kBuffer)]: null, [Symbol(kBufferCb)]: null, [Symbol(kBufferGen)]: null, [Symbol(kBytesRead)]: 0, [Symbol(kBytesWritten)]: 0 }, httpVersionMajor: 1, httpVersionMinor: 1, httpVersion: '1.1', complete: false, headers: { connection: 'upgrade', host: 'XXX', 'x-real-ip': 'XXX', 'content-length': '2936', 'user-agent': 'Go-http-client/1.1', 'content-type': 'application/json', 'accept-encoding': 'gzip' }, rawHeaders: [ 'Connection', 'upgrade', 'Host', 'XXX', 'X-Real-IP', 'XXX', 'Content-Length', '2936', 'User-Agent', 'Go-http-client/1.1', 'Content-Type', 'application/json', 'Accept-Encoding', 'gzip' ], trailers: {}, rawTrailers: [], aborted: false, upgrade: false, url: '/triggerTest', method: 'POST', statusCode: null, statusMessage: null, client: Socket { connecting: false, _hadError: false, _parent: null, _host: null, _readableState: ReadableState { objectMode: false, highWaterMark: 16384, buffer: BufferList { head: null, tail: null, length: 0 }, length: 0, pipes: null, pipesCount: 0, flowing: true, ended: false, endEmitted: false, reading: true, sync: false, needReadable: true, emittedReadable: false, readableListening: false, resumeScheduled: false, paused: false, emitClose: false, autoDestroy: false, destroyed: false, defaultEncoding: 'utf8', awaitDrain: 0, readingMore: false, decoder: null, encoding: null }, readable: true, _events: [Object: null prototype] { end: [Array], timeout: [Function: socketOnTimeout], data: [Function: bound socketOnData], error: [Function: socketOnError], close: [Array], drain: [Function: bound socketOnDrain], resume: [Function: onSocketResume], pause: [Function: onSocketPause] }, _eventsCount: 8, _maxListeners: undefined, _writableState: WritableState { objectMode: false, highWaterMark: 16384, finalCalled: false, needDrain: false, ending: false, ended: false, finished: false, destroyed: false, decodeStrings: false, defaultEncoding: 'utf8', length: 0, writing: false, corked: 0, sync: true, bufferProcessing: false, onwrite: [Function: bound onwrite], writecb: null, writelen: 0, bufferedRequest: null, lastBufferedRequest: null, pendingcb: 0, prefinished: false, errorEmitted: false, emitClose: false, autoDestroy: false, bufferedRequestCount: 0, corkedRequestsFree: [Object] }, writable: true, allowHalfOpen: true, _sockname: null, _pendingData: null, _pendingEncoding: '', server: Server { _events: [Object: null prototype], _eventsCount: 2, _maxListeners: undefined, _connections: 1, _handle: [TCP], _usingWorkers: false, _workers: [], _unref: false, allowHalfOpen: true, pauseOnConnect: false, httpAllowHalfOpen: false, timeout: 120000, keepAliveTimeout: 5000, maxHeadersCount: null, headersTimeout: 40000, _connectionKey: '6::::8449', [Symbol(IncomingMessage)]: [Function: IncomingMessage], [Symbol(ServerResponse)]: [Function: ServerResponse], [Symbol(asyncId)]: 11 }, _server: Server { _events: [Object: null prototype], _eventsCount: 2, _maxListeners: undefined, _connections: 1, _handle: [TCP], _usingWorkers: false, _workers: [], _unref: false, allowHalfOpen: true, pauseOnConnect: false, httpAllowHalfOpen: false, timeout: 120000, keepAliveTimeout: 5000, maxHeadersCount: null, headersTimeout: 40000, _connectionKey: '6::::8449', [Symbol(IncomingMessage)]: [Function: IncomingMessage], [Symbol(ServerResponse)]: [Function: ServerResponse], [Symbol(asyncId)]: 11 }, timeout: 120000, parser: HTTPParser { '0': [Function: parserOnHeaders], '1': [Function: parserOnHeadersComplete], '2': [Function: parserOnBody], '3': [Function: parserOnMessageComplete], '4': [Function: bound onParserExecute], _headers: [], _url: '', socket: [Circular], incoming: [Circular], outgoing: null, maxHeaderPairs: 2000, _consumed: true, onIncoming: [Function: bound parserOnIncoming], parsingHeadersStart: 0 }, on: [Function: socketListenerWrap], addListener: [Function: socketListenerWrap], prependListener: [Function: socketListenerWrap], _paused: false, _httpMessage: ServerResponse { _events: [Object: null prototype], _eventsCount: 1, _maxListeners: undefined, outputData: [], outputSize: 0, writable: true, _last: false, chunkedEncoding: false, shouldKeepAlive: true, useChunkedEncodingByDefault: true, sendDate: true, _removedConnection: false, _removedContLen: false, _removedTE: false, _contentLength: null, _hasBody: true, _trailer: '', finished: false, _headerSent: false, socket: [Circular], connection: [Circular], _header: null, _onPendingData: [Function: bound updateOutgoingData], _sent100: false, _expect_continue: false, req: [Circular], locals: [Object: null prototype] {}, [Symbol(kNeedDrain)]: false, [Symbol(isCorked)]: false, [Symbol(kOutHeaders)]: [Object: null prototype] }, [Symbol(asyncId)]: 40, [Symbol(kHandle)]: TCP { reading: true, onconnection: null, _consumed: true, [Symbol(owner)]: [Circular] }, [Symbol(lastWriteQueueSize)]: 0, [Symbol(timeout)]: Timeout { _idleTimeout: 120000, _idlePrev: [TimersList], _idleNext: [TimersList], _idleStart: 12341, _onTimeout: [Function: bound ], _timerArgs: undefined, _repeat: null, _destroyed: false, [Symbol(refed)]: false, [Symbol(asyncId)]: 41, [Symbol(triggerId)]: 40 }, [Symbol(kBuffer)]: null, [Symbol(kBufferCb)]: null, [Symbol(kBufferGen)]: null, [Symbol(kBytesRead)]: 0, [Symbol(kBytesWritten)]: 0 }, _consuming: false, _dumped: false, next: [Function: next], baseUrl: '', originalUrl: '/triggerTest', _parsedUrl: Url { protocol: null, slashes: null, auth: null, host: null, port: null, hostname: null, hash: null, search: null, query: null, pathname: '/triggerTest', path: '/triggerTest', href: '/triggerTest', _raw: '/triggerTest' }, params: {}, query: {}, res: ServerResponse { _events: [Object: null prototype] { finish: [Function: bound resOnFinish] }, _eventsCount: 1, _maxListeners: undefined, outputData: [], outputSize: 0, writable: true, _last: false, chunkedEncoding: false, shouldKeepAlive: true, useChunkedEncodingByDefault: true, sendDate: true, _removedConnection: false, _removedContLen: false, _removedTE: false, _contentLength: null, _hasBody: true, _trailer: '', finished: false, _headerSent: false, socket: Socket { connecting: false, _hadError: false, _parent: null, _host: null, _readableState: [ReadableState], readable: true, _events: [Object: null prototype], _eventsCount: 8, _maxListeners: undefined, _writableState: [WritableState], writable: true, allowHalfOpen: true, _sockname: null, _pendingData: null, _pendingEncoding: '', server: [Server], _server: [Server], timeout: 120000, parser: [HTTPParser], on: [Function: socketListenerWrap], addListener: [Function: socketListenerWrap], prependListener: [Function: socketListenerWrap], _paused: false, _httpMessage: [Circular], [Symbol(asyncId)]: 40, [Symbol(kHandle)]: [TCP], [Symbol(lastWriteQueueSize)]: 0, [Symbol(timeout)]: Timeout { _idleTimeout: 120000, _idlePrev: [TimersList], _idleNext: [TimersList], _idleStart: 12341, _onTimeout: [Function: bound ], _timerArgs: undefined, _repeat: null, _destroyed: false, [Symbol(refed)]: false, [Symbol(asyncId)]: 41, [Symbol(triggerId)]: 40 }, [Symbol(kBuffer)]: null, [Symbol(kBufferCb)]: null, [Symbol(kBufferGen)]: null, [Symbol(kBytesRead)]: 0, [Symbol(kBytesWritten)]: 0 }, connection: Socket { connecting: false, _hadError: false, _parent: null, _host: null, _readableState: [ReadableState], readable: true, _events: [Object: null prototype], _eventsCount: 8, _maxListeners: undefined, _writableState: [WritableState], writable: true, allowHalfOpen: true, _sockname: null, _pendingData: null, _pendingEncoding: '', server: [Server], _server: [Server], timeout: 120000, parser: [HTTPParser], on: [Function: socketListenerWrap], addListener: [Function: socketListenerWrap], prependListener: [Function: socketListenerWrap], _paused: false, _httpMessage: [Circular], [Symbol(asyncId)]: 40, [Symbol(kHandle)]: [TCP], [Symbol(lastWriteQueueSize)]: 0, [Symbol(timeout)]: Timeout { _idleTimeout: 120000, _idlePrev: [TimersList], _idleNext: [TimersList], _idleStart: 12341, _onTimeout: [Function: bound ], _timerArgs: undefined, _repeat: null, _destroyed: false, [Symbol(refed)]: false, [Symbol(asyncId)]: 41, [Symbol(triggerId)]: 40 }, [Symbol(kBuffer)]: null, [Symbol(kBufferCb)]: null, [Symbol(kBufferGen)]: null, [Symbol(kBytesRead)]: 0, [Symbol(kBytesWritten)]: 0 }, _header: null, _onPendingData: [Function: bound updateOutgoingData], _sent100: false, _expect_continue: false, req: [Circular], locals: [Object: null prototype] {}, [Symbol(kNeedDrain)]: false, [Symbol(isCorked)]: false, [Symbol(kOutHeaders)]: [Object: null prototype] { 'x-powered-by': [Array] } }, body: {}, route: Route { path: '/triggerTest', stack: [ [Layer] ], methods: { post: true } } }
https://canvas.instructure.com/doc/api/file.data_service_setup.html
HTTPS - Webhook with JWT signing
- URL - web service endpoint. The event body is a signed JWT. Beta and Production JWKs can be found here. Most libraries should be able to match the kid in the JWT header to the relevant JWK to validate the signature. If a customer's HTTPS service experiences an outage, the events will not be delivered till the service is recovered.