Overview

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.

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!')
    }
  }
)
🚧

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 once you receive the event. You should store and process this on your end.

HeaderSample Value
Content-Typeapplication/json
User-AgentAscend
X-Ascend-Request-Timestamp1697740606
X-Ascend-Signatureakfhaskfasf....
{
  id: 'ajskljfaklsjd0912132',
  type: 'invoice.paid',
  data: {
    "id": "684c8c8e-75eb-4134-925a-cb3a30f23633",
    "memo": "Policy(s): I13123 (General Liability)",
    "payee": "John Doe Trucking",
    "status": "paid",
    "paid_at": "2023-10-01T23:51:37.507Z",
    "due_date": "2023-10-01",
    "issued_at": "2023-09-30T23:51:34.760Z",
    "insured_id": "700166b3-860d-40e0-8649-bb806e38acgh",
    "payer_name": "John Doe",
    "program_id": "5f77e68e-5649-4d14-8f5d-7d95bacb2323",
    "invoice_url": "https://",
    "invoice_items": [
    {
      "id": "ad26827a-5104-41c4-932a-d7cda47a5bf0",
      "title": "Paid in full for P8045172324",
      "amount_cents": 60,
      "invoice_item_type": "pay_in_full"
    }
    ],
    "invoice_number": "II2DH1HGHJ",
    "payment_method": {
      "card": {
        "brand": "visa",
        "last_four_digits": "4256"
      },
      "payment_type": "card"
    },
    "total_amount_cents": 600000
  }
}