Ascend uses webhooks to notify organizations when an event happens. Ascend will send a POST request to a specified URL over HTTPS with a payload that includes the relevant information related to the event.

📘

Reach out to [email protected] to request a webhook key.

How to handle requests from Ascend?

1. Verify the request authenticity

Webhook requests include a custom header X-Ascend-Signature. The signature is an HMAC with SHA-256. To validate the request manually, you should complete the following steps:

  1. Extract the signature from the header X-Ascend-Signature. The value of the string is a comma-separated string (e.g. t=1657323346,v1=c159a974c819600ed23c9476f94d741a0c5895392627d6736ea3d7bcf4ba26ef).
  2. Construct the string to be signed. The string should be constructed by concatenating the request timestamp and the body of the request separate by a colon (<timestamp>:<stringified request body>). You can read the request timestamp from the X-Ascend-Request-Timestamp header.
  3. Sign the string by computing an HMAC with the SHA256 hash function. Use the secret key provided by Ascend.
const crypto = require('crypto');
const signedString = `${timestamp}:${requestBody}`
const hmac = crypto.createHmac('sha256', 'MY_SECRET')
    .update(signedString)
    .digest('hex');
  1. Build the expected signature by concatenating the request timestamp with the sign string generated in step 3. (e.g. const expectedSignature = `t=${req.header("X-Ascend-Request-Timestamp")},v1=${hmac}`)
  2. Compare the signatures. You should compare the signature produced locally and the signature in the X-Ascend-Signature header.

Full sample code:

app.use((req, res, next,) => {
  // Do not use the webhook parser for the webhook route
  if (req.originalUrl === '/webhook-listener') {
    next();
  } else {
    bodyParser.json()(req, res, next);
  }
});

app.post(
  '/webhook-listener',
  bodyParser.raw({ type: 'application/json' }),
  (req, res) => {
    const requestBody = req.body;
    const requestTimestamp = req.header("X-Ascend-Request-Timestamp")
    const requestSignature = req.header("X-Ascend-Signature")

    const hmac = crypto.createHmac('sha256', SECRET)
        .update(`${requestTimestamp}:${requestBody}`)
        .digest('hex');

    const expectedSignature = `t=${requestTimestamp},v1=${hmac}`

    const event = JSON.parse(requestBody);

    if (requestSignature == expectedSignature) {
      res.status(200).send('success!');
    } else {
      res.status(500).send('error!')
    }
  }
)

🚧

Request body

Make sure you are using the raw payload from the request otherwise it won't be possible to generate the same expected signature.

2. Process the event

The request will include a payload that follows the schema:

{
    "type": "object",
    "properties": {
        "id": {
            "type": "string",
            "description": "A unique identifier for this event"
        },
        "type": {
            "type": "string",
            "description": "The name of the event"
        },
        "data": {
            "type": "object",
            "description": "The event data"
        }
    }
}

The consuming endpoint should be able to handle different event types. By checking the type field, a consumer should be able to differentiate between the different use cases.

3. Return a 200 response

You should send a successful 200 response to Ascend as soon as possible once you receive the event. Any other type of response will be considered a failure and the event will be retried up to 10 times.