In each request that YouLend send to a partner endpoint there will be a X-YL-Webhook-Signature header. Partners can use this signature along with the secret key to verify that the request was sent by YouLend, not by a third party.
Each subscription to a YouLend webhook generates a new secret key and returns it. So, in the case of multiple subscriptions with different endpoints, partners need to use the corresponding secret key to verify requests to each of them.
To verify a request is from YouLend partners need to compute the hash signature and compare this with the signature from the header to ensure they match. Partners can compute the hash signature by hashing the received payload with the SHA-256 function and the secret key shared previously. The following outlines in detail the steps required to verify the signature:
- Extract the base64 encoded signature from the X-YL-Webhook-Signature header. The header is of the form “sha256=AKnj34jY…” so extract accordingly.
- Decode the retrieved signature from base64.
- Compute the expected signature with the SHA-256 function. Use the secret key (which is base 64 encoded) associated with this endpoint as the key and the request payload string as the message.
- Compare your generated hash signature with the decoded one extracted from the header. You may want to use a constant-time string comparison like compare_digest to protect against timing based attacks.
Below you can find test data to "play with" whilst building your code for verifying the webhooks request as well as a code sample on how to perform step 1 to 4
Example response to webhook subscription
This is an example of a response you will receive hitting the
subscription endpoint https://docs.youlend.com/reference/post_webhooksubscription
{
"subscriptionId": "9f0edd0f-4d85-4435-aae9-595b0b95cd35",
"webhookUrl": "https://eo30mixozyogha7.m.pipedream.net",
"webhookSecretKey": "0uUolr+Mimze+3rnlFCtHNvNdiGdqBOrL5OLisW1k187KD4QaPV2froFQSzzqIt2cVRHBNzRBvkGCG3tWQszMw=="
}
Webhook request example
This is an example of a webhook request you would receive if you have subscribed to any of our webhooks. This response was generated by using the specific subscription above, therefore you can use the "webhookSecretKey" to verify the signature of the webhook below.
{
"method": "POST",
"path": "/",
"query": {},
"client_ip": "34.254.141.82",
"url": "https://eo30mixozyogha7.m.pipedream.net/",
"headers": {
"host": "eo30mixozyogha7.m.pipedream.net",
"content-length": "350",
"X-Yl-Webhook-Signature": "sha256=S6s0+kNCXYPUJAwPebDFcP8+eNKZdpfyH6h+M/DkNC4=",
"traceparent": "00-bd79fbfe1ba8197785226f714be1f203-c9dd6f0e1e4e5d52-01",
"content-type": "application/json; charset=utf-8"
},
"body": {
"EventCode": "LOA10013",
"Message": "Lead has received funding",
"EventProperties": {
"LoanId": "fb7b76f6-704f-4a61-a110-eb5cd1fea6b5",
"Sweep": "20.0",
"LeadId": "1de0df20-cf22-41cd-84bc-367c80fc93ac",
"ThirdPartyCustomerId": "12345678",
"DateFunded": "2024-09-07T00:00:00Z",
"LoanNumber": 60198304,
"Amount": "2460.00",
"OriginalAmount": "2828.0"
}
}
}
Code snippets to verify webhook requests
C# code example
[HttpPost]
public ActionResult HandleNotification(object notificationMessage)
{
// Get headers from request
this.HttpContext.Request.Headers.TryGetValue("x-yl-webhook-signature", out var headerList);
// signatureFromHeader should look like: sha256=S8YHSoj6xHqiC7+Wx+FZVzI7ht817b8Alx7VHFqEdV0=
var signatureFromHeader = headerList.First();
// signature should look like: S8YHSoj6xHqiC7+Wx+FZVzI7ht817b8Alx7VHFqEdV0=
var signature = signatureFromHeader.Substring(7);
var signatureAsBytes = Convert.FromBase64String(signature);
// serialisedPayload should look like: "{\"EventCode\":\"ONB10008\",\"Message\":\"asdf\",\"EventProperties\":{\"LendingPartnerId\..."
var serialisedPayload = notificationMessage.ToString();
// Secret key returned by the notification API when you create a subscription
var encodedKey = "0uUolr+Mimze+3rnlFCtHNvNdiGdqBOrL5OLisW1k187KD4QaPV2froFQSzzqIt2cVRHBNzRBvkGCG3tWQszMw==";
// Use the key and the payload to calculate the signature
var calculatedSignatureAsBytes = this.CreateHashOfPayload(encodedKey, serialisedPayload);
// Compare the the signature from the header with the calculated signature to ensure they match
var bytesMatch = signatureAsBytes.SequenceEqual(calculatedSignatureAsBytes);
if (bytesMatch)
{
// Signature from header and signature we calculated match - write your logic that handles the verified webhook
}
return this.Ok();
}
private byte[] CreateHashOfPayload(string key, string payload)
{
var decodedKey = Convert.FromBase64String(key);
// Use key and payload to create the hash
using (HMACSHA256 hmac = new HMACSHA256(decodedKey))
{
byte[] hashValue = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
return hashValue;
}
}
PHP code example
private const SIGNATURE_HEADER = 'X-Yl-Webhook-Signature';
private const SIGNATURE_PREFIX = 'sha256=';
/**
* Verifies the request is authorized by YouLend
*
* @return boolean
*/
public function isAuthorized(): bool
{
$signature_from_header = $this->request->getHeader(self::SIGNATURE_HEADER);
$signature = substr($signature_from_header, strlen(self::SIGNATURE_PREFIX));
$signature_as_bytes = base64_decode($signature);
// Secret key returned by the notification API when the subscription was created
$encoded_secret = $this->getWebhookSecret();
$raw_request_body = file_get_contents('php://input');
// the incoming JSON string from $raw_request_body may include unwanted newlines
// and whitespaces. decoding and then re-encoding normalizes the data. JSON_UNESCAPED_SLASHES
// and JSON_UNESCAPED_UNICODE flags ensure that special characters such as / and Unicode characters
// are not escaped unnecessarily.
$request_body = json_encode(
json_decode(
$raw_request_body ?? ''
),
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
);
// Use the key and the payload to calculate the signature
$calculated_signature_as_bytes = $this->createHashOfPayload($encoded_secret, $request_body);
return ($signature_as_bytes === $calculated_signature_as_bytes);
}
private function createHashOfPayload(string $key, string $payload): string
{
// Decode the base64-encoded key
$decoded_secret = base64_decode($key);
// Use HMAC-SHA256 to create the hash
return hash_hmac('sha256', $payload, $decoded_secret, true);
}