Appearance
Webhook Events
Many of our clients choose to be notified of new prices for their instances via HTTP webhooks as opposed to regularly polling our API.
Security
Webhook Signatures
We use asymmetric cryptographic algorithms to sign every webhook request that leaves the DynamO Pricing system.
Every request contains at least one signature header with the name format x-signature-<algorithm>
, the content of the header is always a DER-encoded signature in hexadecimal format, the webhook can be verified by a public key and this signature.
The actual public keys are available via the API to our authenticated clients. It is practical to cache these keys for offline validation, however we advise against storing the keys for longer periods (e.g. months). We suggest caching the keys for a few minutes up to an hour depending on your security requirements.
The algorithms used for signatures depend on the key, not every request might be signed with the same algorithm and the same key, the algorithm is indicated in the header name and is available via the API for each key.
As of writing this we only use secp256r1
with SHA256 digests with the signature header x-signature-secp256r1-sha256
.
Signature Verification
The requests can be verified as described by the pseudo-code below. We provide examples for a few commonly used languages, but it should be easily adapted to any other language.
python
def verify_request(request):
for each public key do
verify(
from_hex(request.header("X-Signature-{key.algorithm}"))
sha256(
concat(
request.method,
request.path,
[request.query],
request.header("Date"),
[request.body],
)
)
)
if verified:
stop
if no keys could verify:
reject request
ts
import crypto from "crypto";
/**
* Verify a DynamO Pricing webhook request.
*
* @param method The request method.
* @param pathWithQuery The request path with query parameters as it appears in the request.
* @param dateHeader The contents of the `Date` header.
* @param signatureHeader The contents of the signature header (e.g. `X-Signature-secp256r1-sha256`).
* @param body The full unaltered body of the webhook (most likely JSON).
* @param keyPem Public key retrieved from the DynamO Pricing API.
* @returns Whether the webhook is valid.
*/
function verifyRequestSignature(
method: string,
pathWithQuery: string,
dateHeader: string,
signatureHeader: string,
body: Buffer,
keyPem: string
): boolean {
return crypto.verify(
null,
Buffer.concat([
Buffer.from(method.toUpperCase() + pathWithQuery + dateHeader),
body,
]),
keyPem,
Buffer.from(signatureHeader, "hex")
);
}
python
# The ecdsa library warns us about the lack of side-channel attack protection
# and advises against using it in production.
#
# Since we only verify signatures, it should be safe to use.
#
# If it is a concern to you, we suggest using an openssl wrapper library.
from ecdsa import VerifyingKey
from hashlib import sha256
import ecdsa
def verify_request_signature(
method: str,
path_with_query: str,
date_header: str,
signature_header: str,
body: bytes,
key_pem: str,
):
"""
Verify the contents of a DynamO Pricing webhook.
Parameters
----------
method : str
The request's method.
path_with_query : str
The path and query as appears in the request.
date_header : str
The contents of the `Date` header.
signature_header : str
The contents of the signature header (e.g. `X-Signature-secp256r1-sha256`).
body : bytes
The full unaltered body of the request.
key_pem : str
A public key retrieved from the DynamO Pricing API.
Raises
-------
Exception
Raises an exception if the signature is not valid.
"""
vk = VerifyingKey.from_pem(key_pem)
vk.verify(
bytes.fromhex(signature_header),
(
method.upper() +
path_with_query +
date_header
).encode("utf-8") + body,
hashfunc=sha256,
sigdecode=ecdsa.util.sigdecode_der,
)
Replay Attacks
The Date
header contains the date when the request is sent in a format described in RFC5322.
Make sure to reject old requests (e.g. >1min), otherwise bad actors might even be able to prevent prices from changing by repeatedly sending the same "dynamic" prices.
Additional Measures
While we believe webhook signatures are safe enough, we are open to adhere to stricter requirements e.g. by utilizing mTLS or VPNs on client request.