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.
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:
- 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
). - 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 theX-Ascend-Request-Timestamp
header. - Sign the string by computing an
HMAC
with theSHA256
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');
- 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}``) - 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 once you receive the event. You should store and process this on your end.
Sample Webhook Event
Headers
Header | Sample Value |
---|---|
Content-Type | application/json |
User-Agent | Ascend |
X-Ascend-Request-Timestamp | 1697740606 |
X-Ascend-Signature | akfhaskfasf.... |
Body
{
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
}
}
Updated 3 days ago