Fluent Commerce Logo
Docs
Sign In
Essential knowledge

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
    `flex.signature`
    (MD5withRSA) and
    `fluent-signature`
    (SHA512withRSA). 
  • You can use standard cryptography (e.g. in Node) or built-in Java functions to verify webhook calls.

Executing the 

`SendWebhook`
 rule will post the corresponding event message to the endpoint passed as the user’s rule parameter. For an overview of Webhooks, go here.

Fluent Orchestration Engine will then send the event 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 Orchestration Engine.

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`
 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.

Code Examples

There are 2 options for verifying the webhook request:

  • Use the 
    `fluent-api-client`
    's 
    `Event.verify()`
     method (Java only).
  • Use standard cryptography methods provided by the client side language/framework.

Using the Fluent API Client for Signature Verification (Java only)

The 

`verify`
 method uses a public key to verify the signature against the event 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`
:

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.


Copyright © 2024 Fluent Retail Pty Ltd (trading as Fluent Commerce). All rights reserved. No materials on this docs.fluentcommerce.com site may be used in any way and/or for any purpose without prior written authorisation from Fluent Commerce. Current customers and partners shall use these materials strictly in accordance with the terms and conditions of their written agreements with Fluent Commerce or its affiliates.

Fluent Logo