Webhooks
Overview
Webhooks send real-time data to a specified URL when certain events occur. This allows applications to receive instant updates without constant polling.
Webhooks are simply requests made to an endpoint of your choice that you can then implement custom business logic for. So when certain events occur within Credit Key, and there is an endpoint subscribed to that event type, it will make a POST
request to that endpoint with a payload describing the event.
Webhook event catalog
Currently, the following events are available:
Company Events | Description |
---|---|
company.created | When a new company has been created. |
company.status | When a company's underwriting status has changed. |
company.user_added | When a user has been added to a company. |
company.user_removed | When a user has been removed from a company. |
company.user_updated | When a user has been updated for a company. |
Order Events | Description |
---|---|
order.created | When a new order has been created. |
order.updated | When various order attributes have changed. |
For a full list of currently available events, see our API
How to implement webhooks
At a high level, in order to implement webhooks, you'll need to:
- Use our API to register a URL you control to receive webhooks.
- Implement the registered URL as a public
POST
route on your servers that accepts aJSON
webhook payload. This endpoint should:- verify the signature of the request payload to ensure the webhook is from Credit Key.
- grab the relevant pieces of payload you need and then perform your business logic.
Registering an endpoint
Send a request to our API specifying which URL in your system we should use when making webhook requests. For example:
POST /v2/webhook
Payload
{
"url": "https://my-company.com/webhooks/endpoint",
"description": "testing",
"events": [
"company.created"
],
}
You can use our Webhooks API endpoints at any time to manage your webhooks. Such as to: change the events an endpoint subscribes to, or toggle an endpoint as inactive.
Implementing your webhook receiving endpoint
The implementation of the endpoint should be publicly available on the web, and accept POST
requests with a JSON
payload. All endpoints registered with us must use HTTPS
in order to ensure communication is encrypted.
Verify payload signature
Webhook signatures let you verify that webhook messages are actually sent by us and not a malicious actor. We highly recommend verifying the payload signature against the signature sent in each event request header.
Each webhook call includes three headers with additional information that are used for verification:
svix-id
: the unique message identifier for the webhook message. This identifier is unique across all messages, but will be the same when the same webhook is being resent (e.g. due to a previous failure).svix-timestamp
: timestamp in seconds since epoch.svix-signature
: the Base64 encoded list of signatures (space delimited).
You need to use the raw request body when verifying webhooks, as the cryptographic signature is sensitive to even the slightest changes. You should watch out for frameworks that parse the request as JSON
and then stringify it because this too will break the signature verification.
The content to sign is composed by concatenating the id, timestamp (from the headers) and the raw payload, separated by the full-stop character (.
). In code, it will look something like:
signed_content = `${webhook_id}.${webhook_timestamp}.${raw_request_body}`
The signature is sensitive to any changes, so even a small change in the body will cause the signature to be completely different. This means that you should not change the body in any way before verifying.
To calculate the computed signature, HMAC the content with a base64 decoded version of the secret. Here is an example in Node.js:
const crypto = require('crypto');
const signing_secret = "whsec_9SLm2YhQvfiLE3iw0nodKtuMRGvyp001".split('_')[1]; // we only care about the part _after_ the `whsec_` prefix
const content = `${webhook_id}.${webhook_timestamp}.${raw_request_body}`
const base64DecodedSecret = Buffer.from(signing_secret, 'base64');
const signature = crypto
.createHmac('sha256', base64DecodedSecret)
.update(content)
.digest('base64');
console.log(signature);
Signing secrets
After registering your endpoints via the API, you can log into our Merchant Portal to view the signing secrets associated to each endpoint.
This generated signature should match one of the ones sent in the svix-signature
header.
The svix-signature
header is composed of a list of space delimited signatures and their corresponding version identifiers. The signature list is most commonly of length one. Though there could be any number of signatures; especially when signing secrets are being rotated. For example:
v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo= v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo=
Make sure to remove the version prefix and delimiter (e.g. v1,
) before verifying the signature.
Please note that to compare the signatures it's recommended to use a constant-time comparison method in order to prevent timing attacks.
Verify timestamp
As mentioned above, we also send the timestamp of the attempt in the svix-timestamp
header. You should compare this timestamp against your system timestamp and make sure it's within your tolerance in order to prevent timestamp attacks.
Verified source IPs
In case your webhook receiving endpoint is behind a firewall or NAT, you may need to allow traffic from these IP addresses. This could also be useful as an additional layer to ensure webhook requests into your system only originate from us.
44.228.126.217
50.112.21.217
52.24.126.164
54.148.139.208
2600:1f24:64:8000::/52
Retries
We attempts to deliver each webhook message based on a retry schedule with exponential backoff.
The schedule
Each message is attempted based on the following schedule, where each period is started following the failure of the preceding attempt:
- Immediately
- 5 seconds
- 5 minutes
- 30 minutes
- 2 hours
- 5 hours
- 10 hours
- 10 hours (in addition to the previous)
If an endpoint is removed or disabled delivery attempts to the endpoint will be disabled as well.
For example, an attempt that fails three times before eventually succeeding will be delivered roughly 35 minutes and 5 seconds following the first attempt.
Troubleshooting
There are some common reasons why your webhook endpoint is failing:
Not using the raw payload body
This is the most common issue. When generating the signed content, you must use the raw string body of the message payload.
If you convert JSON
payloads into strings using methods like stringify, different implementations may produce different string representations of the JSON
object, which can lead to discrepancies when verifying the signature. It's crucial to verify the payload exactly as it was sent, byte-for-byte or string-for-string, to ensure accurate verification.
Missing the secret key
From time to time we see people simple using the wrong secret key. Remember that keys are unique to endpoints.
Sending the wrong response codes
When we receive a response with a 2xx status code, we interpret that as a successful delivery even if you indicate a failure in the response payload. Make sure to use the right response status codes so we know when messages are supposed to succeed vs fail.
Responses timing out
We will consider any message that fails to send a response within 15 seconds to be a failed message. If your endpoint is also processing complicated workflows, it may timeout and result in failed messages.
We suggest having your endpoint simply receive the message and add it to a queue to be processed asynchronously so you can respond promptly and avoiding getting timed out.
Updated 25 days ago