Skip to content

Function

Function plugin allows using JavaScript (ECMAScript 5.1+) to customize inbound behavior, and implement features like signature verification and request body transformation.

Syntax

javascript
function handle() {
	// ...
}

Examples

Verify GitHub signature and transform event

javascript
function handle() {
  // verify signature
  var bytes = webhookx.utils.hmac('SHA-256', "secret", webhookx.request.getBody())
  var signature = "sha256=" + webhookx.utils.encode('hex', bytes)
  var signatureHeader = webhookx.request.getHeader("X-Hub-Signature-256")
  console.log(signature)
  if (!webhookx.utils.digestEqual(signature, signatureHeader)) {
    webhookx.response.exit(400, { 'Content-Type': 'application/json' }, { message: 'invalid signature' })
  }
  // transform payload
  try {
    var obj = JSON.parse(webhookx.request.getBody())
    var event_type = webhookx.request.getHeader('X-GitHub-Event') + "." + obj.action
    webhookx.request.setBody(JSON.stringify({ event_type: event_type, data: obj }))
  } catch (e) {
    console.log(e)
    webhookx.response.exit(400, { 'Content-Type': 'application/json' }, { message: 'Invalid JSON' })
  }
}

Verify Stripe signature and transform event

javascript
function handle() {
  // verify signature
  let parsed = parse(webhookx.request.getHeader("Stripe-Signature"))

  var signedPayload = `${parsed.timestamp}.${webhookx.request.getBody()}`
  var bytes = webhookx.utils.hmac('SHA-256', "whsec_EE55F...", signedPayload)
  var exceptedSignature = webhookx.utils.encode('hex', bytes)

  var valid = false
  for (let signature of parsed.signatures) {
      if (webhookx.utils.timingSafeEqual(exceptedSignature, signature)) {
          valid = true
          break
      }
  }
  if (!valid) {
      webhookx.response.exit(400, {'Content-Type': 'application/json'}, {message: 'Invalid Signature'})
      return
  }

  // transform payload
  try {
      var obj = JSON.parse(webhookx.request.getBody())
      webhookx.request.setBody(JSON.stringify({event_type: obj.type, data: obj}))
  } catch (e) {
      console.log(e)
      webhookx.response.exit(400, {'Content-Type': 'application/json'}, {message: 'Invalid JSON'})
  }
}

Configuration

shell
curl -X POST 'http://localhost:8080/workspaces/default/plugins' \
--header 'Content-Type: application/json' \
--data '{
    "name": "function",
    "source_id": "{source_id}",
    "config": {
        "function": "function handle() { console.log('Hello World') }"
    }
}'
yaml
sources:
  - name: default-source
    path: /
    methods: [ "POST" ]
    plugins:
      - name: function
        config:
          function: |
            function handle() { 
            	console.log('Hello World')
            }

Limitations

  • The require is not supported.
  • The execution runtime is limited to 1 second.
  • The function script is limited to 1 MB.
  • The execution runtime cannot perform any IO, or access any external resources such as the network or file system.

Built-in functions

The following methods are supported out of the box.

  • JSON.parse()

  • JSON.stringify()

  • console.log()

Additional objects

The additional objects provides interaction with the internal functionality.

webhookx.request

This module provides a set of functions for retrieving information from incoming requests.

webhookx.request.getHost()

Returns the hostname of the request. The value is either the value of the "Host" header or the hostname given in request URL itself.

Returns

  • string: The hostname.

Usage

javascript
/*
GET /path HTTP/1.1
Host: example.com
*/

webhookx.request.getHost() // "example.com"

webhookx.request.getMethod()

Returns the method of the request.

Returns

  • string: The request method.

Usage

javascript
/*
POST /path HTTP/1.1
*/

webhookx.request.getMethod() // "POST"

webhookx.request.getPath()

Returns the normalized path component of the request’s URL.

Returns

  • string: The normalized request URL path.

Usage

javascript
/*
GET /api HTTP/1.1
Host: example.com
*/

webhookx.request.getPath() // "/api"

webhookx.request.getHeaders()

Returns an object containing all request headers.

Returns

  • object: The request headers.

Usage

javascript
/*
GET / HTTP/1.1
Host: example.com
X-Foo: bar1
X-Foo: bar2
*/

var headers = webhookx.request.getHeaders()
headers['Host']   // "example.com"
headers['X-Foo']  // "bar1,bar2"

webhookx.request.getHeader(name)

Returns the first value of the specified request header.

Parameters

  • name (string): The name of the header to be returned.

Returns

  • string | null: The value of the header that first occurrence, or null if not present.

Usage

javascript
/*
GET / HTTP/1.1
Host: example.com
X-Foo: bar1
X-Foo: bar2
*/

webhookx.request.getHeader('Host') 				// "example.com"
webhookx.request.getHeader('X-Foo') 			// "bar1"
webhookx.request.getHeader('X-Unknown') 	// null

webhookx.request.getBody()

Returns the request body.

Returns

  • string: The request body.

Usage

javascript
/*
POST / HTTP/1.1
Content-Type: application/json

{"foo":"bar"}
*/

webhookx.request.getBody() // '{"foo":"bar"}'

webhookx.request.setBody(body)

Sets the request body.

Parameters

  • body (string): The new body to be set.

Usage

javascript
webhookx.request.setBody('{"foo":"bar","key":"value"}')

webhookx.response

webhookx.response.exit(status, headers, body)

Terminates a request with given response.

Parameters

  • status (number): The response code.
  • headers (object | null): The response headers.
  • body (object | string): The response body. Passing object body will be converted to JSON.

Usage

javascript
webhookx.response.exit(200, nil, 'OK')

webhookx.response.exit(200, 
  { 'Content-Type': 'application/json' }, 
  { message: 'OK' }
)

webhookx.utils

This module provides a set of utility functions.

webhookx.utils.hmac(algorithm, key, data)

Computes the digest of data with the key using a hash algorithm.

Parameters

  • algorithm (string): The hash algorithm. SHA-1, SHA-256, SHA-512, and MD5 are supported.
  • key (string): The key.
  • data (string): The data.

Returns

  • Uint8Array: The digest byte array.

Usage

javascript
webhookx.utils.hmac('SHA-1', 'secret', 'string') // [238 138 201 13 45 114 136 90 66 71 248 106 221 234 76 60 41 164 203 164]

webhookx.utils.encode(encoding, data)

Encodes data with a encoding.

Parameters

  • encoding (string): The available encoding are hex, base64, and base64url.
  • data (Uint8Array | string): The data to be encoded.

Returns

  • string: The encoded string.

Usage

javascript
webhookx.utils.encode('hex', 'string') // 737472696e67
webhookx.utils.encode('hex', webhookx.utils.hmac('SHA-1', 'secret', 'string')) // ee8ac90d2d72885a4247f86addea4c3c29a4cba4

webhookx.utils.timingSafeEqual(str1, str2)

Returns whether str1 is equal to str2 in a timing-safe manner.

Note: using constant-time string comparison can prevent timing attacks, especially regarding signature comparison.

Parameters

  • str1 (string)
  • str2 (string)

Returns

  • boolean

Usage

javascript
webhookx.utils.timingSafeEqual('foo', 'foo') // true
webhookx.utils.timingSafeEqual('foo', 'foobar') // false

webhookx.log

This module provides a set of functions about log.

webhookx.log.LEVEL(message)

Writes message into WebhookX log using a log level. The supported levels are

  • webhookx.log.debug(message)
  • webhookx.log.info(message)
  • webhookx.log.warn(message)
  • webhookx.log.error(message)

Usage

javascript
webhookx.log.debug("debug message")
webhookx.log.info("info message")
webhookx.log.warn("warn message")
webhookx.log.error("error message")