Please find various code examples below.
General integration documentation can be found here.
Please find various code examples below.
General integration documentation can be found here.
<?php | |
/** | |
This page uses the "Bouncer" integration. | |
It checks for the existance of the OPT paremeter before displaying content. | |
If OPT does not exist, redirect to Opticks with next_url parameter. | |
**/ | |
// https://opticks.io/r/{campaign_hash}?next_url={abc}&external_id={xyz}&subpublisher_id={}&var1={...}&var2={...}&var3={...} | |
/** | |
* @return string The current URL built from the server variables | |
*/ | |
function getThisUrl() | |
{ | |
// parse this page's URL | |
$url = (isset($_SERVER['HTTPS']) ? "https" : "http") . ":" . "//{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"; | |
$urlParts = parse_url($url); | |
// build this page's URL | |
$thisUrl = "{$urlParts['scheme']}://{$urlParts['host']}:{$urlParts['port']}{$urlParts['path']}?"; | |
if (isset($urlParts['query'])) { | |
$thisUrl .= "{$urlParts['query']}"; | |
} | |
if (isset($urlParts['fragment'])) { | |
$thisUrl .= $urlParts['fragment']; | |
} | |
return $thisUrl; | |
} | |
// AES constants | |
define("CIPHER", "AES-128-CBC"); | |
define("KEY", "1234567812345678"); // KEEP THIS PRIVATE! | |
$opticksEndpoint = "http://geoff.billysrv.com:8000/unjcv5un"; | |
$campaignHash = "asdfqwerty12345"; | |
$redirectEndpoint = "$opticksEndpoint/g/$campaignHash"; | |
// where do I expect to receive Opticks data | |
// e.g. http://myurl.com?opticks_analysis={OPT}&opticks_id={click_id} | |
$opticksInfo = [ | |
'OPT' => 'opticks_analysis', | |
'click_id' => 'opticks_id' | |
]; | |
$encryptedAnalysis = $_GET[$opticksInfo['OPT']] ?? null; | |
$opticksClickId = $_GET[$opticksInfo['click_id']] ?? null; | |
// no encrypted response from Opticks | |
if (!$encryptedAnalysis) { | |
$nextUrl = getThisUrl(); | |
// Tell Opticks where to put the OPT data and opticks click_id | |
foreach ($opticksInfo as $k => $v) { | |
$nextUrl .= '&' . $v . '={' . $k . '}'; | |
} | |
$nextEncodedUrl = urlencode($nextUrl); | |
// map my URL params to Opticks params | |
$merchantIdUrlParam = "merchant"; | |
$subPublisherIdUrlParam = "source"; | |
$merchantClickIdUrlParam = "pixel"; | |
$someOtherIdUrlParam = "other"; | |
if (($q = parse_url($nextUrl)['query'])) { | |
parse_str($q, $getArray); | |
$external_id = $getArray[$merchantClickIdUrlParam] ?? ''; | |
$subpublisher_id = $getArray[$subPublisherIdUrlParam] ?? ''; | |
$var1 = $getArray[$merchantIdUrlParam] ?? ''; | |
$var2 = $getArray[$someOtherIdUrlParam] ?? ''; | |
} | |
$nextLocation = "$redirectEndpoint?next_url=$nextEncodedUrl"; | |
$nextLocation .= "&external_id=$external_id&subpublisher_id=$subpublisher_id"; | |
$nextLocation .= "&var1=$var1&var2=$var2"; | |
// redirect user to Opticks for analysis | |
header("Location: $nextLocation"); | |
/* Make sure that code below does not get executed when we redirect. */ | |
exit; | |
} else { | |
// OPT parameter exists, decode it | |
$iv = substr($opticksClickId ?? '', -16); | |
$OPT = rawurldecode($encryptedAnalysis); | |
try { | |
if (!$original_plaintext = openssl_decrypt($OPT, CIPHER, KEY, 0, $iv)) { | |
throw new \Exception("Unable to decrypt OPT."); | |
} | |
$optDecoded = json_decode($original_plaintext); | |
if (!is_object($optDecoded) || filter_var($optDecoded->ts, FILTER_VALIDATE_INT) === false) { | |
var_dump($optDecoded); | |
throw new \Exception("OPT decrypted incorrectly."); | |
} | |
} catch (\Exception $e) { | |
var_dump(['OPT' => $OPT, 'cipher' => CIPHER, 'key' => KEY, 'iv' => $iv, 'plaintext' => $original_plaintext]); | |
die($e->getMessage()); | |
} | |
} | |
var_dump([$optDecoded, getThisUrl()]); | |
?> |
public String decrypt(byte[] encryptedData, String iv, String key) { | |
String decryptedString = null; | |
try { | |
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); | |
SecretKeySpec skeyspec = new SecretKeySpec(key.substring(0, 16).getBytes(), "AES"); | |
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.substring(iv.length() - 16, iv.length()).getBytes()); | |
decryptCipher.init(Cipher.DECRYPT_MODE, skeyspec, ivParameterSpec); | |
byte[] decrypted = decryptCipher.doFinal(encryptedData); | |
decryptedString = new String(decrypted); | |
} catch (Exception e) { | |
log.error("Error decrypting string:" + e.getLocalizedMessage()); | |
} | |
return decryptedString; | |
} |
<!-- | |
Capture data returned from the opticksEvent and | |
add it to the form for server-side decryptio.n | |
--> | |
<!--DOCTYPE html--> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Subscribe</title> | |
<script | |
src="https://code.jquery.com/jquery-3.3.1.min.js" | |
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" | |
crossorigin="anonymous"> | |
</script> | |
<script> | |
window.addEventListener('opticksEvent', function (event) { | |
var detail = event.detail; | |
var opticksClickId = document.createElement('input'); | |
opticksClickId.type = 'hidden'; | |
opticksClickId.name = 'opticksClickId'; | |
opticksClickId.value = event.detail.opticksClickId; | |
document.querySelector('form').appendChild(opticksClickId); | |
var opticksInfo = document.createElement('input'); | |
opticksInfo.type = 'hidden'; | |
opticksInfo.name = 'opticksInfo'; | |
opticksInfo.value = event.detail.data; | |
document.querySelector('form').appendChild(opticksInfo); | |
var opticksHmac = document.createElement('input'); | |
opticksHmac.type = 'hidden'; | |
opticksHmac.name = 'opticksHmac'; | |
opticksHmac.value = event.detail.hmac; | |
document.querySelector('form').appendChild(opticksHmac); | |
// optional | |
$.ajax({ | |
method: "POST", | |
url: "verifyAndDecryptAjaxWithRedirect.php", | |
data: JSON.stringify(detail), | |
success: function (response) { | |
console.log(response); | |
} | |
}); | |
}, true); | |
</script> | |
</head> | |
<body> | |
<script src="https://track.opticks.io/j/327709153eb8162cd" | |
data-opticks="subpublisher_id={source}&var1={affiliate_id}&version=v2"> | |
</script> | |
<form method="post" action="verifyDecryptForm.php"> | |
<input name="fullName"/> | |
<button type="submit">Submit</button> | |
</form> | |
</body> | |
</html> |
<!-- | |
Capture data returned from the opticksEvent and | |
add it to the form for server-side decryptio.n | |
--> | |
<!--DOCTYPE html--> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Subscribe</title> | |
</head> | |
<body> | |
<script> | |
(function () { | |
window.addEventListener('opticksEvent', function (event) { | |
var opticksClickId = document.createElement('input'); | |
opticksClickId.type = 'hidden'; | |
opticksClickId.name = 'opticksClickId'; | |
opticksClickId.value = event.detail.opticksClickId; | |
document.querySelector('form').appendChild(opticksClickId); | |
var opticksInfo = document.createElement('input'); | |
opticksInfo.type = 'hidden'; | |
opticksInfo.name = 'opticksInfo'; | |
opticksInfo.value = event.detail.data; | |
document.querySelector('form').appendChild(opticksInfo); | |
var opticksHmac = document.createElement('input'); | |
opticksHmac.type = 'hidden'; | |
opticksHmac.name = 'opticksHmac'; | |
opticksHmac.value = event.detail.hmac; | |
document.querySelector('form').appendChild(opticksHmac); | |
}, true); | |
var opticksScript = document.createElement('script'); | |
opticksScript.src = 'https://track.opticks.io/j/327709153eb8162cd'; | |
opticksScript.setAttribute('data-opticks', 'subpublisher_id={source}&var1={affiliate_id}&version=v2'); | |
document.body.insertBefore(opticksScript, document.body.childNodes[0]); | |
})(); | |
</script> | |
<form method="post" action="verifyDecryptForm.php"> | |
<input name="fullName"/> | |
<button type="submit">Submit</button> | |
</form> | |
</body> | |
</html> |
<?php | |
require "vendor/autoload.php"; | |
try { | |
// load variables from INI file | |
$ini = parse_ini_file("../opticks.ini"); | |
define('CIPHER', 'AES-128-CBC'); | |
// default key is 1234567812345678 | |
define('KEY', $ini['secret']); | |
// read posted data | |
if(!$body = file_get_contents('php://input')) { | |
throw new \InvalidArgumentException("Body is empty."); | |
} | |
if(!$posted = json_decode($body)) { | |
throw new \InvalidArgumentException("Body is not valid JSON. ". $body); | |
} | |
$opticksClickId = $posted->opticksClickId; | |
$iv = substr($opticksClickId, -16); | |
$hmac64 = $posted->hmac; | |
$encryptedData64 = $posted->data; | |
// Calculate HMAC of encrypted data | |
$calcmac = hash_hmac('sha256', $encryptedData64, KEY, $as_binary = true); | |
$calcmac64 = base64_encode($calcmac); | |
// Confirm HMAC | |
if (!hash_equals($hmac64, $calcmac64)) { | |
throw new \InvalidArgumentException("Message verification (hmac) failure."); | |
} | |
// Decrypt | |
if(!$plaintext = openssl_decrypt($encryptedData64, CIPHER, KEY, 0, $iv)) { | |
throw new \UnexpectedValueException("Message decryption failure."); | |
} | |
// Extra check that decrypted info is valid JSON | |
if (!$plainJson = json_decode($plaintext)) { | |
throw new \UnexpectedValueException("Encrypted data is not valid JSON. " . $body); | |
} | |
// Compare plain text clickid to encrypted clickid | |
if($plainJson->opticksClickId != $opticksClickId) { | |
throw new \UnexpectedValueException("Opticks click ids don't match."); | |
} | |
// Check timestamp sanity - adjust according to your needs | |
$tsDiff = microtime(1) - $plainJson->ts / 1000; | |
if($tsDiff > 1.5) { | |
throw new \OutOfBoundsException("timestamp problem: " . microtime(1) . " - " . $plainJson->ts / 1000 . " = $tsDiff"); | |
} | |
try { | |
// Ensure clickid is unique using Redis key/value store - adjust to your environment | |
$redis = new Predis\Client('tcp://' . $ini['redis_host']); | |
if ($redis->get($opticksClickId)) { | |
throw new \UnexpectedValueException("Opticks click id has already been used."); | |
} | |
$redis->publish("opticks", $plaintext); | |
} catch (\Exception $e) { | |
// redis doesn't work | |
} | |
// persist data | |
// don't do this in production | |
header('Content-Type: application/json'); | |
echo $plaintext; | |
} catch (\Exception $e) { | |
header("HTTP/1.1 500 ERROR"); | |
echo "Error: " . $e->getMessage(); | |
} |
<?php | |
require "vendor/autoload.php"; | |
$detections = []; | |
try { | |
$ini = parse_ini_file("../opticks.ini"); | |
define('CIPHER', 'AES-128-CBC'); | |
// default key is 1234567812345678 | |
define('KEY', $ini['secret']); | |
// ensure required variables exist | |
if(!$opticksClickId = $_REQUEST['opticksClickId']) { | |
throw new \InvalidArgumentException("Missing opticksClickId."); | |
} | |
if (!$encryptedData64 = $_REQUEST['opticksInfo']) { | |
throw new \InvalidArgumentException("Missing opticksInfo."); | |
} | |
if (!$hmac64 = $_REQUEST['opticksHmac']) { | |
throw new \InvalidArgumentException("Missing opticksHmac."); | |
} | |
// Prepare crypto | |
$iv = substr($opticksClickId, -16); | |
$calcmac = hash_hmac('sha256', $encryptedData64, KEY, $as_binary = true); | |
$calcmac64 = base64_encode($calcmac); | |
// Verify crypto signature | |
if (!hash_equals($hmac64, $calcmac64)) { | |
throw new \UnexpectedValueException("Message verification (hmac) failure."); | |
} | |
// Decrypt | |
if(!$plaintext = openssl_decrypt($encryptedData64, CIPHER, KEY, 0, $iv)) { | |
throw new \UnexpectedValueException("Message decryption failure."); | |
} | |
// Convert payload to object | |
if(!$plainJson = json_decode($plaintext)) { | |
throw new \InvalidArgumentException("Encrypted data is not valid JSON. " . $body); | |
} | |
// Compare encrypted opticksClickId to plain text opticksClickId | |
if($plainJson->opticksClickId != $opticksClickId) { | |
throw new \UnexpectedValueException("Opticks click ids don't match."); | |
} | |
// simple timestamp check, adjust to your needs | |
$tsDiff = microtime(1) - $plainJson->ts / 1000; | |
if($tsDiff > 600) { // 10 minutes | |
throw new \OutOfBoundsException("timestamp problem: " . microtime(1) . " - " . $plainJson->ts / 1000 . " = $tsDiff"); | |
} | |
// Ensure clickid is unique using Redis key/value store - adjust to your environment | |
$redis = new Predis\Client('tcp://'.$ini['redis_host']); | |
$redis->incr($opticksClickId); | |
if($redis->get($opticksClickId) > 1) { | |
throw new \UnexpectedValueException("Opticks click id has already been used."); | |
} | |
// Ensure there are no invalidClickReasons | |
if ($plainJson->invalidClickReasons) { | |
throw new \UnexpectedValueException("invalidClickReasons exist."); | |
} | |
foreach ($plainJson->analysis->detections as $detection) { | |
$detections = array_merge($detections, $detection->triggers); | |
} | |
//var_dump($detections); | |
//echo $plaintext; | |
/** Do subscription stuff below **/ | |
} catch (\Exception $e) { | |
// don't return error string in production | |
// maybe redirect to $plainJson->fallbackUrl ? | |
header("HTTP/1.1 500 ERROR"); | |
echo "Error: " . $e->getMessage(); | |
} |