Author:
Fluent Commerce
Changed on:
30 July 2024
An overview of how webhooks work in Fluent Commerce, how they are signed, and validated, and what public keys are used.
`flex.signature`
(MD5withRSA) and `fluent-signature`
(SHA512withRSA). Executing the `SendWebhook`
will post the corresponding message to the endpoint passed as the user’s parameter. For an overview of Webhooks, go here.
Fluent Engine will then send the message to the specified endpoint via `POST`
. The request includes a Request Signature to allow the client to assert the validity of the request from the Fluent Engine.
The Signature can be found in the Request Header and is generated by encrypting the message with a private key and with either an MD5 or SHA512-based algorithm.
The receiver should respond quickly with a 200 OK status. Please see the Webhook Retries section, in the overview, for more info on how the Fluent platform handles timeouts and retries.
Delays should be done asynchronously - i.e., by responding success, and having the client post a new with result/response details to an orchestrated , should there be further logic required thereafter.
If the connection times out, the same request will be retried 3 times within 14 seconds.
For security purposes, Fluent Commerce cryptographically signs each request with a private key before sending it to the target endpoint.
To validate that the request came from Fluent Commerce and has not been tampered with or replaced by another third party, a public key is provided so that clients can verify that the signatures match the request
Every request from Fluent contains two custom headers.
`flex.signature`
`fluent-signature`
Both headers contain a hash using the body and the private key of the environment from which this request was generated.
The difference between the two is the algorithm used to generate the hash: `flex.signature`
uses `MD5withRSA`
while `fluent-signature`
uses `SHA512withRSA`
.
Clients can use Fluent Commerce public key (x509 base64 encoded) to verify the message using either of the signatures based on their preferred hashing algorithm.
There are 2 options for verifying the request:
`fluent-api-client`
's `Event.verify()`
method (Java only).The `verify`
method uses a public key to verify the signature against the body. You can easily access the public key via the `fluent-api-client`
.
To validate the incoming request, use the `Event.verify`
method provided by the `fluent-api-client`
:
1import com.fluentretail.rubix.event.Event;
2
3//@Controller...
4public class MyWebhookReceiver {
5
6 private final static String FLUENT_PUBLIC_KEY = "<Relevant Environment Public Key as provided above>";
7
8 private final static String MD5_WITH_RSA = "MD5withRSA";
9 private final static String MD5_HEADER = "flex.signature";
10
11 //@RequestMapping...
12 public Response fluentOrderStatusUpdate(@RequestBody Event event) {
13
14 ObjectMapper objectMapper = new ObjectMapper();
15 String jsonEvent = objectMapper.writeValueAsString(event);
16
17 verifyWebhook(jsonEvent, MD5_WITH_RSA, Request.getHeader(MD5_HEADER));
18
19 // logic...
20
21 return Response; // Success Response
22 }
23
24 private boolean verifyWebhook(String jsonEvent, String algorithm, String signature) {
25
26 FluentApiClient apiClient; // initialize api client
27
28 Event.verify(jsonEvent, apiClient.getEnvironment().getPublicKey(), signature, algorithm);
29 }
30}
1import com.fluentretail.rubix.event.Event;
2
3//@Controller...
4public class MyWebhookReceiver {
5
6 private final static String FLUENT_PUBLIC_KEY = "<Relevant Environment Public Key as provided above>";
7
8 private final static String SHA_WITH_RSA = "SHA512withRSA";
9 private final static String SHA_HEADER = "fluent-signature";
10
11 //@RequestMapping...
12 public Response fluentOrderStatusUpdate(@RequestBody Event event) {
13
14 ObjectMapper objectMapper = new ObjectMapper();
15 String jsonEvent = objectMapper.writeValueAsString(event);
16
17 verifyWebhook(jsonEvent, SHA_WITH_RSA, Request.getHeader(SHA_HEADER));
18
19 // logic...
20
21 return Response; // Success Response
22 }
23
24 private boolean verifyWebhook(String jsonEvent, String algorithm, String signature) {
25
26 FluentApiClient apiClient; // initialize api client
27
28 Event.verify(jsonEvent, apiClient.getEnvironment().getPublicKey(), signature, algorithm);
29 }
30}
To verify the request without the use of the Fluent API Client, use the standard cryptography methods provided by the language or framework of your client code.
You will need to use the following Public Keys for verification:
Below is an example of client side code using cryptographic methods to verify a signature using Java (though any language can be used):
1import javax.validation.ValidationException;
2import javax.xml.bind.DatatypeConverter;
3import java.security.KeyFactory;
4import java.security.PublicKey;
5import java.security.Signature;
6import java.security.spec.X509EncodedKeySpec;
7
8//@Controller...
9public class MyWebhookReceiver {
10
11 private final static String FLUENT_PUBLIC_KEY = "<Relevant Environment Public Key as provided above>";
12
13 private final static String MD5_WITH_RSA = "MD5withRSA";
14 private final static String MD5_HEADER = "flex.signature";
15
16 //@RequestMapping...
17 public Response fluentOrderStatusUpdate(@RequestBody Event event) {
18
19 ObjectMapper objectMapper = new ObjectMapper();
20 String jsonEvent = objectMapper.writeValueAsString(event);
21
22 verifyWebhook(jsonEvent, MD5_WITH_RSA, Request.getHeader(MD5_HEADER));
23
24 // logic...
25
26 return Response; // Success Response
27 }
28
29 private boolean verifyWebhook(String jsonEvent, String algorithm, String signature) {
30
31 // for flex.signature
32 Signature sig = Signature.getInstance(algorithm);
33 byte[] keyBytes = DatatypeConverter.parseBase64Binary(FLUENT_PUBLIC_KEY);
34 X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
35 KeyFactory kf = KeyFactory.getInstance("RSA");
36 PublicKey pubKey = kf.generatePublic(spec);
37 sig.initVerify(pubKey);
38 sig.update(jsonEvent.getBytes());
39 return sig.verify(DatatypeConverter.parseBase64Binary(signature));
40 }
41}
1import javax.validation.ValidationException;
2import javax.xml.bind.DatatypeConverter;
3import java.security.KeyFactory;
4import java.security.PublicKey;
5import java.security.Signature;
6import java.security.spec.X509EncodedKeySpec;
7
8//@Controller...
9public class MyWebhookReceiver {
10
11 private final static String FLUENT_PUBLIC_KEY = "<Relevant Environment Public Key as provided above>";
12
13 private final static String SHA_WITH_RSA = "SHA512withRSA";
14 private final static String SHA_HEADER = "fluent-signature";
15
16 //@RequestMapping...
17 public Response fluentOrderStatusUpdate(@RequestBody Event event) {
18
19 ObjectMapper objectMapper = new ObjectMapper();
20 String jsonEvent = objectMapper.writeValueAsString(event);
21
22 verifyWebhook(jsonEvent, SHA_WITH_RSA, Request.getHeader(SHA_HEADER));
23
24 // logic...
25
26 return Response; // Success Response
27 }
28
29 private boolean verifyWebhook(String jsonEvent, String algorithm, String signature) {
30
31 // for flex.signature
32 Signature sig = Signature.getInstance(algorithm);
33 byte[] keyBytes = DatatypeConverter.parseBase64Binary(FLUENT_PUBLIC_KEY);
34 X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
35 KeyFactory kf = KeyFactory.getInstance("RSA");
36 PublicKey pubKey = kf.generatePublic(spec);
37 sig.initVerify(pubKey);
38 sig.update(jsonEvent.getBytes());
39 return sig.verify(DatatypeConverter.parseBase64Binary(signature));
40 }
41}
1const crypto = require('crypto');
2const NodeRSA = require('node-rsa');
3
4const fullRequestBodyMd5= <webhook-payload>;
5const signatureValueMd5 = <flex.signature>;
6const publicKey = "-----BEGIN PUBLIC KEY-----\n" + <environment-key> + "\n-----END PUBLIC KEY-----";
7
8//node rsa
9const keyMd5NodeRsa = new NodeRSA(publicKey);
10keyMd5NodeRsa.setOptions({signingScheme: 'md5'});
11console.warn("NodeRsa with Md5: " + keyMd5NodeRsa.verify(fullRequestBodyMd5, signatureValueMd5, null, 'base64'));
12
13//crypto
14const verifierRsa = crypto.createVerify('RSA-MD5');
15verifierRsa.update(fullRequestBodyMd5);
16console.warn("Crypto with Md5: " + verifierRsa.verify(publicKey, Buffer.from(signatureValueMd5, 'base64')));
1const signatureValueSha512 = '<fluent-signature>';
2const fullRequestBodySha512 = '<webhook-payload>';
3
4//node rsa
5const keySha512NodeRsa = new NodeRSA(publicKey);
6keySha512NodeRsa.setOptions({signingScheme: 'sha512'});
7console.warn("NodeRsa with Sha512: " + keySha512NodeRsa.verify(fullRequestBodySha512, signatureValueSha512, null, 'base64'));
8
9//crypto
10const verifierSha256 = crypto.createVerify('RSA-SHA512');
11verifierSha256.update(fullRequestBodySha512);
12console.warn("Crypto with Sha521: " + verifierSha256.verify(publicKey, Buffer.from(signatureValueSha512, 'base64')));
The following public keys are provided for verification in each shared environment and region.
Each key is x509 base64 encoded.