Generate and Validate Signature
About
Signature
is a security parameter that needs to be generated on merchant's Backend to verify the request authenticity.
Here is the case the Signature
is used:
- When merchant hits DOKU endpoints
- Merchant generate Signature from Request Header : Merchant must generate the signature in request header and DOKU will verify the authenticity.
- Merchant validate Signature from Response Header : DOKU must generate the signature in response header and Merchant will verify the authenticity.
- When DOKU hits merchant endpoints (HTTP Notification / Inquiry Request).
Merchant must also verify theSignature
that DOKU sends on the request header when DOKU Notification hits merchantNotification URL
, to verify the notification request is coming from DOKU. You can check the detail for signature in HERE
Signature components from Request Header
To generate a Signature
in request header, merchant need to prepare these components:
Client-Id:value
Request-Id:value
Request-Timestamp:value
Request-Target:value
Digest:value
Component Explanation
Name | Description |
---|---|
Client-Id | Retrieved from the Request Header |
Request-Id | Retrieved from the Request Header |
Request-Timestamp | Retrieved from the Request Header |
Request-Target | The path of the endpoint that will be hitted e.g: /doku-virtual-account/v2/payment-code .NOTE: For the HTTP Notification from DOKU to merchant server, this will be the path of merchant Notification URL . As for the Inquiry Request , this will be the path of merchant Inquiry URL |
Digest | Encoded (base64) value of hashed (SHA-256) JSON body. This component only applied for POST Method. |
Preparation
Before generating Signature
, merchant need to prepare all the component required.
Set Client-Id, Request-Id, Request-Timestamp.
Use the Client-Id, Request-Id, Request-Timestamp that is placed on the Request Header.
Set Request-Target
The Request-Target is depending on who is sending the request:
- When merchant hits DOKU endpoints: The Request-Target is the path of the DOKU API that merchant hits.
For instance, if merchant wants to hit DOKU VA API:https://api.doku.com/doku-virtual-account/v2/payment-code
. Therefore, the Request-Target value is/doku-virtual-account/v2/payment-code
. - When DOKU hits merchant endpoints (HTTP Notification / Inquiry Request): The Request-Target is the path of merchant
Notification URL
or theInquiry URL
.
For instance, if merchant set theNotification URL
:https://yourdomain.com/payments/notifications
. Therefore, the Request-Target value is/payments/notifications
.
Generate Digest
Digest
is the hashed of the request body. To generate the Digest
:
- Calculate SHA256 base64 hash from the JSON Body
Generating Signature
After all the Signature
component has been set, merchant can now generate it:
- Arrange the signature components to one component and its value per line by adding
\n
escape character. Don't add\n
at the end of the string. Sample of the raw format:
Client-Id:MCH-0001-10791114622547\nRequest-Id:cc682442-6c22-493e-8121-b9ef6b3fa728\nRequest-Timestamp:2020-08-11T08:45:42Z\nRequest-Target:/doku-virtual-account/v2/payment-code\nDigest:5WIYK2TJg6iiZ0d5v4IXSR0EkYEkYOezJIma3Ufli5s=
This is how merchant see it:
Client-Id:MCH-0001-10791114622547
Request-Id:cc682442-6c22-493e-8121-b9ef6b3fa728
Request-Timestamp:2020-08-11T08:45:42Z
Request-Target:/doku-virtual-account/v2/payment-code
Digest:5WIYK2TJg6iiZ0d5v4IXSR0EkYEkYOezJIma3Ufli5s=
- Calculate HMAC-SHA256 base64 from all the components above using the Secret Key from DOKU Back Office
- Put encoded value and prepend
HMACSHA256=
to theSignature
. Sample:
Signature: HMACSHA256=OvIRJs/jH8BIcGsktr4d8nnYtxY6E0Uzdm9d1GVgv5s=
Signature Component from Response Header
To validate a signature
in response header, merchant need to see and check these components.
Client-Id:value
Request-Id:value
Response-Timestamp:value
Request-Target:value
Digest:value
Component Explanation
Name | Description |
---|---|
Client-Id | Retrieved from the Request Header |
Request-Id | Retrieved from the Request Header |
Response-Timestamp | Retrieved from the Response Header |
Request-Target | The path of the endpoint that will be hitted e.g: /doku-virtual-account/v2/payment-code . |
Digest | Encoded (base64) value of hashed (SHA-256) JSON body. This component only applied for POST Method. |
Preparation
Before validating Signature
, merchant need to check all the component required.
Set Client-Id, Request-Id, Response-Timestamp.
Use the Client-Id, Request-Id, Response-Timestamp that is placed on the Response Header.
Set Request-Target
The Request-Target is depending on who is sending the request:
- When merchant hits DOKU endpoints: The Request-Target is the path of the DOKU API that merchant hits.
Validating Signature
After merchant send request to DOKU and generate signature in request header, DOKU will send response and generate signature in response header. Then merchant can verify this response is coming from DOKU by Signature.
- Arrange the signature components to one component and its value per line by adding
\n
escape character. Don't add\n
at the end of the string. Sample of the raw format:
Client-Id:MCH-0001-10791114622547\nRequest-Id:cc682442-6c22-493e-8121-b9ef6b3fa728\Response-Timestamp:2020-08-11T08:45:42Z\nRequest-Target:/doku-virtual-account/v2/payment-code\nDigest:5WIYK2TJg6iiZ0d5v4IXSR0EkYEkYOezJIma3Ufli5s=
This is how merchant see it:
Client-Id:MCH-0001-10791114622547
Request-Id:cc682442-6c22-493e-8121-b9ef6b3fa728
Response-Timestamp:2020-08-11T08:45:42Z
Request-Target:/doku-virtual-account/v2/payment-code
Digest:5WIYK2TJg6iiZ0d5v4IXSR0EkYEkYOezJIma3Ufli5s=
- Calculate HMAC-SHA256 base64 from all the components above using the Secret Key from DOKU Back Office
- Put encoded value and prepend
HMACSHA256=
to theSignature
. Sample:
Signature: HMACSHA256=OvIRJs/jH8BIcGsktr4d8nnYtxY6E0Uzdm9d1GVgv5s=
info
To make sure every response API from DOKU, just verify in Signature that you get from Response Header!
API with GET Method
For API that uses GET
method such as, Check Status API, merchant don't need to generate a Digest
.
- Arrange the signature components to one component and its value per line by adding
\n
escape character. Don't add\n
at the end of the string. Sample of the raw format:
Client-Id:MCH-0001-10791114622547\nRequest-Id:d895fb53-479c-4f77-a76a-ab81b40d77cb\nRequest-Timestamp:2020-08-11T08:45:42Z\nRequest-Target:/orders/v1/status/INV-123123-12313
This is how merchant see it:
Client-Id:MCH-0001-10791114622547
Request-Id:d895fb53-479c-4f77-a76a-ab81b40d77cb
Request-Timestamp:2020-08-11T08:45:42Z
Request-Target:/orders/v1/status/INV-123123-12313
- Calculate HMAC-SHA256 base64 from all the components above using the Secret Key from DOKU Back Office
- Put encoded value and prepend
HMACSHA256=
to theSignature
. Sample:
Signature: HMACSHA256=B1cKBzk/aB1AXADCZkq135bnktxY1o02zmmdd2cVgf12=
Sample code
Here is the sample code to generate the Signature
:
Sample Only
This is just a sample code to demonstrate how to generate the Signature on different programming language. Kindly adjust the code to suited your project's structure.
- Java
- PHP
- Python
- Node.js
- Ruby
- Go
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class Signature {
public static final String CLIENT_ID = "Client-Id";
public static final String REQUEST_ID = "Request-Id";
public static final String REQUEST_TIMESTAMP = "Request-Timestamp";
public static final String REQUEST_TARGET = "Request-Target";
public static final String DIGEST = "Digest";
public static final String COLON_SYMBOL = ":";
public static final String NEW_LINE = "\n";
// Generate Digest
public static String generateDigest(String requestBody) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(requestBody.getBytes(StandardCharsets.UTF_8));
byte[] digest = md.digest();
return Base64.getEncoder().encodeToString(digest);
}
private static String generateSignature(String clientId, String requestId, String requestTimestamp, String requestTarget, String digest, String secret) throws InvalidKeyException, NoSuchAlgorithmException {
// Prepare Signature Component
System.out.println("----- Component Signature -----");
StringBuilder component = new StringBuilder();
component.append(CLIENT_ID).append(COLON_SYMBOL).append(clientId);
component.append(NEW_LINE);
component.append(REQUEST_ID).append(COLON_SYMBOL).append(requestId);
component.append(NEW_LINE);
component.append(REQUEST_TIMESTAMP).append(COLON_SYMBOL).append(requestTimestamp);
component.append(NEW_LINE);
component.append(REQUEST_TARGET).append(COLON_SYMBOL).append(requestTarget);
// If body not send when access API with HTTP method GET/DELETE
if(digest != null && !digest.isEmpty()) {
component.append(NEW_LINE);
component.append(DIGEST).append(COLON_SYMBOL).append(digest);
}
System.out.println(component.toString());
System.out.println();
// Calculate HMAC-SHA256 base64 from all the components above
byte[] decodedKey = secret.getBytes();
SecretKey originalKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "HmacSHA256");
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
hmacSha256.init(originalKey);
hmacSha256.update(component.toString().getBytes());
byte[] HmacSha256DigestBytes = hmacSha256.doFinal();
String signature = Base64.getEncoder().encodeToString(HmacSha256DigestBytes);
// Prepend encoded result with algorithm info HMACSHA256=
return "HMACSHA256="+signature;
}
// Sample of Usage
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
String jsonBody = new JSONObject()
.put("order", new JSONObject()
.put("invoice_number", "INV-20210124-0001")
.put("amount", 15000)
)
.put("virtual_account_info", new JSONObject()
.put("expired_time", 60)
.put("amount", 15000)
)
.toString();
// Generate Digest from JSON Body, For HTTP Method GET/DELETE don't need generate Digest
System.out.println("----- Digest -----");
String digest = generateDigest(jsonBody);
System.out.println(digest);
System.out.println();
// Generate Signature
String headerSignature = generateSignature(
"yourClientId",
"yourRequestId",
"2020-10-21T03:38:28Z",
"/request-target/goes-here", // For merchant request to DOKU, use DOKU path here. For HTTP Notification, use merchant path here
digest, // Set empty string for this argumentes if HTTP Method is GET/DELETE
"secret-key-from-DOKU-back-office");
System.out.println("----- Header Signature -----");
System.out.println(headerSignature);
}
}
<?php
$clientId = "yourClientId";
$requestId = "yourRequestId";
$requestDate = "2020-10-21T03:38:28Z";
$targetPath = "/request-target/goes-here"; // For merchant request to Jokul, use Jokul path here. For HTTP Notification, use merchant path here
$secretKey = "secret-key-from-jokul-back-office";
$requestBody = array (
'order' => array (
'amount' => 15000,
'invoice_number' => 'INV-20210124-0001',
),
'virtual_account_info' => array (
'expired_time' => 60,
'reusable_status' => false,
'info1' => 'Merchant Demo Store',
),
'customer' => array (
'name' => 'Taufik Ismail',
'email' => 'taufik@example.com',
),
);
// Generate Digest
$digestValue = base64_encode(hash('sha256', json_encode($requestBody), true));
echo "Digest: " . $digestValue;
echo "\r\n\n";
// Prepare Signature Component
$componentSignature = "Client-Id:" . $clientId . "\n" .
"Request-Id:" . $requestId . "\n" .
"Request-Timestamp:" . $requestDate . "\n" .
"Request-Target:" . $targetPath . "\n" .
"Digest:" . $digestValue;
echo "Component Signature: \n" . $componentSignature;
echo "\r\n\n";
// Calculate HMAC-SHA256 base64 from all the components above
$signature = base64_encode(hash_hmac('sha256', $componentSignature, $secretKey, true));
echo "Signature: " . $signature;
echo "\r\n\n";
// Sample of Usage
$headerSignature = "Client-Id:" . $clientId ."\n".
"Request-Id:" . $requestId . "\n".
"Request-Timestamp:" . $requestDate ."\n".
// Prepend encoded result with algorithm info HMACSHA256=
"Signature:" . "HMACSHA256=" . $signature;
echo "your header request look like: \n".$headerSignature;
echo "\r\n\n";
import hashlib
import hmac
import base64
# Generate Digest
def generateDigest(jsonBody):
return base64.b64encode(hashlib.sha256(jsonBody.encode('utf-8')).digest()).decode("utf-8")
def generateSignature(clientId, requestId, requestTimestamp, requestTarget, digest, secret):
# Prepare Signature Component
print("----- Signature Component -----")
componentSignature = "Client-Id:" + clientId
componentSignature += "\n"
componentSignature += "Request-Id:" + requestId
componentSignature += "\n"
componentSignature += "Request-Timestamp:" + requestTimestamp
componentSignature += "\n"
componentSignature += "Request-Target:" + requestTarget
# If body not send when access API with HTTP method GET/DELETE
if digest:
componentSignature += "\n"
componentSignature += "Digest:" + digest
print(componentSignature)
message = bytes(componentSignature, 'utf-8')
secret = bytes(secret, 'utf-8')
# Calculate HMAC-SHA256 base64 from all the components above
signature = base64.b64encode(hmac.new(secret, message, digestmod=hashlib.sha256).digest()).decode("utf-8")
# Prepend encoded result with algorithm info HMACSHA256=
return "HMACSHA256="+signature
# Sample of usage
# Generate Digest from JSON Body, For HTTP Method GET/DELETE don't need generate Digest
print("----- Digest -----")
jsonBody = '{\"order\":{\"invoice_number\":\"INV-20210124-0001\",\"amount\":150000},\"virtual_account_info\":{\"expired_time\":60,\"reusable_status\":false,\"info1\":\"Merchant Demo Store\"},\"customer\":{\"name\":\"Taufik Ismail\",\"email\":\"taufik@example.com\"}}'
digest = generateDigest(jsonBody)
print(digest)
print("")
# Generate Signature
headerSignature = generateSignature(
"yourClientId",
"yourRequestId",
"2020-10-21T03:38:28Z",
"/request-target/goes-here", # For merchant request to Jokul, use Jokul path here. For HTTP Notification, use merchant path here
digest, # Set empty string for this argumentes if HTTP Method is GET/DELETE
"secret-key-from-jokul-back-office")
print("----- Header Signature -----")
print(headerSignature)
const crypto = require('crypto');
// Generate Digest
function generateDigest(jsonBody) {
let jsonStringHash256 = crypto.createHash('sha256').update(jsonBody,"utf-8").digest();
let bufferFromJsonStringHash256 = Buffer.from(jsonStringHash256);
return bufferFromJsonStringHash256.toString('base64');
}
function generateSignature(clientId, requestId, requestTimestamp, requestTarget, digest, secret) {
// Prepare Signature Component
console.log("----- Component Signature -----")
let componentSignature = "Client-Id:" + clientId;
componentSignature += "\n";
componentSignature += "Request-Id:" + requestId;
componentSignature += "\n";
componentSignature += "Request-Timestamp:" + requestTimestamp;
componentSignature += "\n";
componentSignature += "Request-Target:" + requestTarget;
// If body not send when access API with HTTP method GET/DELETE
if (digest) {
componentSignature += "\n";
componentSignature += "Digest:" + digest;
}
console.log(componentSignature.toString());
console.log();
// Calculate HMAC-SHA256 base64 from all the components above
let hmac256Value = crypto.createHmac('sha256', secret)
.update(componentSignature.toString())
.digest();
let bufferFromHmac256Value = Buffer.from(hmac256Value);
let signature = bufferFromHmac256Value.toString('base64');
// Prepend encoded result with algorithm info HMACSHA256=
return "HMACSHA256="+signature
}
// Sample of Usage
// Generate Digest from JSON Body, For HTTP Method GET/DELETE don't need generate Digest
console.log("----- Digest -----");
let jsonBody = '{\"order\":{\"invoice_number\":\"INV-20210124-0001\",\"amount\":150000},\"virtual_account_info\":{\"expired_time\":60,\"reusable_status\":false,\"info1\":\"Merchant Demo Store\"},\"customer\":{\"name\":\"Taufik Ismail\",\"email\":\"taufik@example.com\"}}';
let digest = generateDigest(jsonBody);
console.log(digest);
console.log();
// Generate Header Signature
let headerSignature = generateSignature(
"yourClientId",
"yourRequestId",
"2020-10-21T03:38:28Z",
"/request-target/goes-here", // For merchant request to Jokul, use Jokul path here. For HTTP Notification, use merchant path here
digest, // Set empty string for this argumentes if HTTP Method is GET/DELETE
"secret-key-from-jokul-back-office")
console.log("----- Header Signature -----")
console.log(headerSignature)
require 'openssl'
require 'base64'
require 'digest'
# Generate Digest
def generateDigest(jsonBody)
return Base64.encode64(Digest::SHA256.digest(jsonBody)).strip()
end
def generateSignature(clientId, requestId, requestTimestamp, requestTarget, digest, secret)
# Prepare Signature Component
puts "----- Component Signature -----"
componentSignature = ("Client-Id:" + clientId")
componentSignature.concat("\n")
componentSignature.concat("Request-Id:" + requestId)
componentSignature.concat("\n")
componentSignature.concat("Request-Timestamp:" + requestTimestamp)
componentSignature.concat("\n")
componentSignature.concat("Request-Target:" + requestTarget)
# If body not send when access API with HTTP method GET/DELETE
unless digest.to_s.strip.empty?
componentSignature.concat("\n")
componentSignature.concat("Digest:" + digest)
end
puts componentSignature
puts "\n"
# Calculate HMAC-SHA256 base64 from all the components above
hash = OpenSSL::HMAC.digest("sha256", secret, componentSignature)
signature = Base64.encode64(hash).strip()
# Prepend encoded result with algorithm info HMACSHA256=
return "HMACSHA256="+signature
end
# Sample of Usage
# Generate Digest from JSON Body, For HTTP Method GET/DELETE don't need generate Digest
puts "----- Digest -----"
jsonBody = '{\"order\":{\"invoice_number\":\"INV-20210124-0001\",\"amount\":150000},\"virtual_account_info\":{\"expired_time\":60,\"reusable_status\":false,\"info1\":\"Merchant Demo Store\"},\"customer\":{\"name\":\"Taufik Ismail\",\"email\":\"taufik@example.com\"}}'
digest = generateDigest(jsonBody)
puts digest
puts "\n"
# Generate Header Signature
headerSignature = generateSignature(
"yourClientId",
"yourRequestId",
"2020-10-21T03:38:28Z",
"/request-target/goes-here", # For merchant request to Jokul, use Jokul path here. For HTTP Notification, use merchant path here
digest, # Set empty string for this argumentes if HTTP Method is GET/DELETE
"secret-key-from-jokul-back-office")
puts "----- Header Signature -----"
puts headerSignature
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"strings"
)
const CLIENT_ID = "Client-Id"
const REQUEST_ID = "Request-Id"
const REQUEST_TIMESTAMP = "Request-Timestamp"
const REQUEST_TARGET = "Request-Target"
const DIGEST = "Digest"
const SYMBOL_COLON = ":"
// Generate Digest
func generateDigest(jsonBody string) string {
converted := []byte(jsonBody)
hasher := sha256.New()
hasher.Write(converted)
return (base64.StdEncoding.EncodeToString(hasher.Sum(nil)))
}
func generateSignature(clientId string, requestId string, requestTimestamp string, requestTarget string, digest string, secret string) string {
// Prepare Signature Component
fmt.Println("----- Component Signature -----")
var componentSignature strings.Builder
componentSignature.WriteString(CLIENT_ID + SYMBOL_COLON + clientId)
componentSignature.WriteString("\n")
componentSignature.WriteString(REQUEST_ID + SYMBOL_COLON + requestId)
componentSignature.WriteString("\n")
componentSignature.WriteString(REQUEST_TIMESTAMP + SYMBOL_COLON + requestTimestamp)
componentSignature.WriteString("\n")
componentSignature.WriteString(REQUEST_TARGET + SYMBOL_COLON + requestTarget)
componentSignature.WriteString("\n")
componentSignature.WriteString(DIGEST + SYMBOL_COLON +digest)
// If body not send when access API with HTTP method GET/DELETE
if len(digest) > 0 {
componentSignature.WriteString("\n")
componentSignature.WriteString(DIGEST + SYMBOL_COLON +digest)
}
fmt.Println(componentSignature.String())
fmt.Println("")
// Calculate HMAC-SHA256 base64 from all the components above
key := []byte(secret)
h := hmac.New(sha256.New, key)
h.Write([]byte(componentSignature.String()))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
// Prepend encoded result with algorithm info HMACSHA256=
return "HMACSHA256="+signature
}
// Sample of Usage
func main() {
// Genreate Digest from JSON Body
var jsonBody = '{\"order\":{\"invoice_number\":\"INV-20210124-0001\",\"amount\":150000},\"virtual_account_info\":{\"expired_time\":60,\"reusable_status\":false,\"info1\":\"Merchant Demo Store\"},\"customer\":{\"name\":\"Taufik Ismail\",\"email\":\"taufik@example.com\"}}'
digest := generateDigest(jsonBody);
fmt.Println("----- Digest -----")
fmt.Println(digest)
fmt.Println("")
// Generate Signature
headerSignature := generateSignature(
"yourClientId",
"yourRequestId",
"2020-10-21T03:38:28Z",
"/request-target/goes-here", // For merchant request to DOKU, use DOKU path here. For HTTP Notification, use merchant path here
digest, // Set empty string for this argumentes if HTTP Method is GET/DELETE
"secret-key-from-DOKU-back-office")
fmt.Println("----- Header Signature -----")
fmt.Println(headerSignature)
}