Introduction to Webhooks

Webhooks act as alerts for API events, delivered as they happen.
The Kulipa platform forwards these alerts to an endpoint residing in your environment, specifically set up to accept and handle them.

Create a webhook subscription to receive POST requests from Kulipa when events associated with your application occur.
For managing multiple webhook endpoints and directing various event alerts to each, generate several webhook subscriptions and organize each to relay a particular event notification to a designated endpoint.

πŸ“˜

Info

Although the majority of applications keep a single webhook subscription, it is possible to have up to five active webhook subscriptions in the Sandbox environment, and a maximum of three in the Production environment simultaneously.

Retry mechanism

If your endpoint fails to respond with a 2xx HTTP status code, Kulipa will retry sending the webhook notification for up to 9 times. The retry interval is as follows: 1, 5, 15 minutes then 1, 4, 12 hours and finally 1, 2, 4 days.

Automatic suspension of a webhook subscription

Kulipa will automatically halt subscribed webhook endpoints that become unreachable.
This measure aids in guaranteeing that unreachable endpoints don't lead to hindrances or problems in the notification delivery for other API users.
Webhook subscriptions may be resumed by invoking this endpoint

IP whitelisting

To ensure the security of your integration, only trust Kulipa webhook notifications from the following IP addresses:

SandboxProduction
xxx.xxx.xxx.xxxxxx.xxx.xxx.xxx

Signature Verification

πŸ“˜

Info

ECDSA (Elliptic Curve Digital Signature Algorithm) is a cryptographic algorithm used to sign and verify digital messages.
It leverages the properties of elliptic curves to provide strong security while using smaller key sizes compared to traditional methods like RSA.
ECDSA is commonly used in blockchain technology and secure communications.

1. Extract headers signature info

Each webhook notification comes digitally signed with an asymmetric key. Within the headers of every webhook, there will be:

  1. x-kulipa-signature: a header containing the digital signature generated by Kulipa
  2. x-kulipa-signature-ts: a header containing the timestamp generated by Kulipa to avoid replay attack
  3. x-kulipa-key-id: a header containing the UUID used for retrieving a public key for signed messages

2. Fetch public key

With the x-kulipa-key-id value, access the following endpoint to obtain the public key and algorithm utilized for signing the message:

Query

# Replace ${apiKey} with your API key
# Replace ${KulipaKeyId} with your public key id extracted from the header 'x-kulipa-key-id'
curl --request GET \
    --url '/v1/webhooks/keys/${KulipaKeyId}' \
    -H 'accept: application/json' \
    -H 'x-api-key: ${apiKey}'

Response

{
  "data": {
    "id": "${KulipaKeyId}",
    "algorithm": "ECDSA_SHA_256",
    "publicKey": {
      "key": "{publicKeyValueFromKulipa}",
      "type": "spki",
      "format": "pem"
    },
    "createdAt": "{creationDateTimeTZ}"
  }
}

3. Verify body integrity

πŸ“˜

Info

Ensure that the webhook notification is in the correct JSON string format before verifying it.

With the signature extracted from the headers x-kulipa-signature, the timestamp from x-kulipa-signature-ts, use the public key and algorithm to verify the integrity of the webhook's payload.

The signed payload is created by concatenating:

  • The timestamp (as a string)
  • The character .
  • The actual JSON payload which is the request raw body

Verify from all parameters that the signature provided in the header is valid:

const crypto = require('crypto');

const publicKey = data.publicKey.key;
const publicKeyType = data.publicKey.type;
const publicKeyFormat = data.publicKey.format;
const rawBody = request.body;
const signature = request.headers['x-kulipa-signature'];
const timestamp = request.headers['x-kulipa-signature-ts'];
const signedPayload = `${timestamp}.${rawBody}`;

const verify = crypto.createVerify('SHA256');
verify.update(signedPayload);
verify.end();
const success = verify.verify(
  {
    key: publicKey,
    type: publicKeyType,
    format: publicKeyFormat,
  },
  signature,
  'hex',
);
console.log(success);

πŸ“˜

Info

To achieve an equality match, calculate the disparity between the current timestamp and the one received, and then determine if this difference falls within your acceptable range.