Webhook Receivers
Author:
Fluent Commerce
Changed on:
30 July 2024
Overview
An overview of how webhooks work in Fluent Commerce, how they are signed, and validated, and what public keys are used.
Key points
- For security purposes, Fluent Commerce cryptographically signs each webhook request with a private key before sending it to the target endpoint.
- Every webhook request from Fluent contains two custom headers (MD5withRSA) and
`flex.signature`
(SHA512withRSA).`fluent-signature`
- You can use standard cryptography (e.g. in Node) or built-in Java functions to verify webhook calls.
Executing the
`SendWebhook`
Fluent Orchestration Engine will then send the event message to the specified endpoint via
`POST`
The Signature can be found in the Request Header and is generated by encrypting the event message with a private key and with either an MD5 or SHA512-based algorithm.
The webhook receiver should respond quickly with a 200 OK status. Please see the Webhook Retries section, in the webhook 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 event with result/response details to an orchestrated Ruleset, should there be further orchestration logic required thereafter.
If the connection times out, the same request will be retried 3 times within 14 seconds.
Webhook Signature Validation
For security purposes, Fluent Commerce cryptographically signs each webhook 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 webhook request from Fluent contains two custom headers.
`flex.signature`
`fluent-signature`
Both headers contain a hash using the event 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`
`MD5withRSA`
`fluent-signature`
`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.
Code Examples
There are 2 options for verifying the webhook request:
- Use the 's
`fluent-api-client`
method (Java only).`Event.verify()`
- Use standard cryptography methods provided by the client side language/framework.
Using the Fluent API Client for Signature Verification (Java only)
The
`verify`
`fluent-api-client`
To validate the incoming request, use the
`Event.verify`
`fluent-api-client`
Java
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}
Language: json
Name: Java Verification Example: flex.signature - MD5withRSA
Description:
[Warning: empty required content area]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}
Language: json
Name: Java Verification Example: fluent-signature - SHA512withRSA
Description:
[Warning: empty required content area]Using Standard Cryptography Methods for Signature Verification
To verify the webhook 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 webhook signature using Java (though any language can be used):
Java
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}
Language: json
Name: Java Verification Example: flex.signature - MD5withRSA
Description:
[Warning: empty required content area]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}
Language: java
Name: Java Verification Example: fluent-signature - SHA512withRSA
Description:
[Warning: empty required content area]NodeJS
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')));
Language: json
Name: NodeJS Verification Example: flex.signature - MD5withRSA
Description:
[Warning: empty required content area]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')));
Language: javascript
Name: NodeJS Verification Example: fluent-signature - SHA512withRSA
Description:
[Warning: empty required content area]Public Keys
The following public keys are provided for Webhook verification in each shared environment and region.
Each key is x509 base64 encoded.