Webhooks: Difference between revisions
No edit summary |
No edit summary |
||
Line 72: | Line 72: | ||
define('DATABASE_PASSWORD', 'somepassword'); | define('DATABASE_PASSWORD', 'somepassword'); | ||
define('DATABASE_DATABASE', 'webhooks'); | define('DATABASE_DATABASE', 'webhooks'); | ||
// Get the raw POST data | // Get the raw POST data | ||
Line 272: | Line 155: | ||
} | } | ||
} | } | ||
} | |||
$embed['image'] = [ | |||
"url" => "https://caspervend.casperdns.com/img.php?u=".$data['event']['product']['texture']."&g=SLIFE" | |||
]; | |||
$embed['description'] = '**Paid:** L$' . $data['event']['money']['gross']. "\n". | |||
'**Received:** L$' . $data['event']['money']['received']. "\n". | |||
'**Location:** ' . $data['event']['vendor']['location']. "\n"; | |||
if ($data['event']['avatars']['recipient']['uuid'] !== $data['event']['avatars']['purchaser']['uuid']) | |||
{ | |||
$embed['description'] = "**As a gift for " . $data['event']['avatars']['recipient']['name'] . "**\n" . $embed['description']; | |||
} | |||
if ($data['event']['flags']['giftCard'] === true || $data['event']['flags']['giftCardV3'] === true) { | |||
$embed['description'] .= "*This was a gift card purchase*\n"; | |||
} | } | ||
} | } | ||
Line 277: | Line 177: | ||
{ | { | ||
$embed = [ | $embed = [ | ||
"title" => $data['event'][' | "title" => $data['event']['PayerName'] . ' bought a ' . $data['event']['ItemName'] . ' from the Marketplace', | ||
"color" => | "color" => 814798, | ||
"description" => "**Paid** L$" .$data['event']['PaymentGross']. "\n**Fee** L$" . $data['event']['PaymentFee'] | |||
]; | ]; | ||
if ($data['event']['ReceiverKey'] !== $data['event']['PayerKey']) | |||
{ | |||
$embed['description'] = "**As a gift for " . $data['event']['ReceiverNAme'] . "**\n" . $embed['description']; | |||
} | |||
} | } | ||
else | else | ||
Line 289: | Line 195: | ||
} | } | ||
$json_data = json_encode([ | $json_data = json_encode([ |
Revision as of 15:03, 29 September 2020
Our Webhook system (launched in 2018) is a beefed-up version of CasperVend's ANS notification system. This is an advanced system which requires programming knowledge. It's designed to reliably send events to server(s) under your control.
Benefits
- * Increased reliability. If your server goes offline, we'll keep sending until it goes through (though see the caveats below)
- * Not just for vendor sales, but can also notify for marketplace sales and redeliveries
- * Not just for CasperVend, but will be rolled out for CasperLet, CasperSafe, CasperUpdate, etc. in the future.
- * JSON encoded so can be read by nearly all backend frameworks
- * In the event of a failure to reach your endpoint, we will notify your avatar by IM (after 5 attempts).
- * SL Marketplace notifications include the matched CasperVend product ID (zero if not associated).
Events Currently Tracked
- * Product purchase inworld
- * SL Marketplace purchase
Tracking Coming Soon For
- * Redeliveries
- * Update Delivery
(This list will be added to and corrected as features and additional event tracking are built in.)
Caveats
- * If we can't get through to your server, we will try again on an exponential backoff. The first 5 attempts happen every minute, but after that the length of time doubles for each attempt.
- * If we still cannot get through to your server after 43 attempts (approximately 24 hours), we will drop all pending notifications and disable your webhook.
- * Because of the retry mechanism, your code must be prepared to accept duplicate requests.
- * We send events in date order, so if an early event fails, you won't receive any later events until your endpoint responds correctly.
- * Your endpoint must respond within 3 seconds.
Getting Help
This is an advanced feature; you are expected to know how to write and debug your own scripts, so we won't help you do that.
However, if you need a different kind of help - or if you think something is wrong at our end, do a ticket to Casper. The rest of the support staff are NOT scripters and CANNOT assist you with this.
Example Code
MySQL
Should work with any modern MySQL daemon. Tested with MariaDB 10.2.12
CREATE DATABASE IF NOT EXISTS `webhooks`;
USE `webhooks`;
CREATE TABLE IF NOT EXISTS `events` (
`eventID` char(36) NOT NULL,
`received` datetime NOT NULL DEFAULT current_timestamp(),
`processed` tinyint(1) unsigned NOT NULL DEFAULT 0,
`data` mediumblob DEFAULT NULL,
PRIMARY KEY (`eventID`),
KEY `processed` (`processed`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
PHP
This code assumes that you have a database created using the SQL above.
<?php
// Configuration options
define('WEBHOOK_SALT', 'YOUR SALT HERE');
define('ENABLE_DEBUG', true);
define('DEBUG_EMAIL', 'your@email.address');
define('DATABASE_HOST', '127.0.0.1');
define('DATABASE_USER', 'webhooks');
define('DATABASE_PASSWORD', 'somepassword');
define('DATABASE_DATABASE', 'webhooks');
// Get the raw POST data
$postData = file_get_contents("php://input");
// Integrity check
if (!isset($_SERVER['HTTP_X_CASPER_WEBHOOK_INTEGRITY_HASH']) || $_SERVER['HTTP_X_CASPER_WEBHOOK_INTEGRITY_HASH'] !== sha1($postData) || !isset($_SERVER['HTTP_X_CASPER_WEBHOOK_VERIFY_HASH']))
{
http_response_code(400);
die('Corruption detected');
}
// Now we check if the request is actually intended for us
$hashCheck = sha1(sha1($postData).":".WEBHOOK_SALT);
if ($_SERVER['HTTP_X_CASPER_WEBHOOK_VERIFY_HASH'] !== $hashCheck)
{
http_response_code(403);
if (ENABLE_DEBUG) mail(DEBUG_EMAIL, 'Webhook Script Error', 'Unauthorised request');
die('Unauthorised');
}
// By this stage we have a validated webhook event. Now decode the json
$data = json_decode($postData, true);
if ($data === false)
{
// JSON decode failure. This should never happen since the payload is signed..
if (ENABLE_DEBUG) mail(DEBUG_EMAIL, 'Webhook Script Error', 'JSON decode failure');
die('Invalid payload');
}
echo "OK";
if (function_exists('fastcgi_finish_request'))
{
// This releases the request so Casper's servers don't have to wait for discord
fastcgi_finish_request();
}
$timestamp = date("c", strtotime("now"));
$embed = [];
$eventType = $data['metadata']['eventType'];
if ($eventType === 'vendor_sale')
{
if ($data['event']['flags']['luckyChair'] === TRUE)
{
$embed = [
"title" => $data['event']['avatars']['purchaser']['name'] . ' won a ' . $data['event']['product']['productName'] . ' from a Lucky Chair!',
"color" => 15746887
];
}
else
{
if ($data['event']['flags']['midnightMadness'] === TRUE)
{
$embed = [
"title" => $data['event']['avatars']['purchaser']['name'] . ' won a ' . $data['event']['product']['productName'] . ' from a Midnight Madness board!',
"color" => 16426522
];
}
else
{
if ($data['event']['flags']['gatcha'] === TRUE)
{
$embed = [
"title" => $data['event']['avatars']['purchaser']['name'] . ' won a ' . $data['event']['product']['productName'] . ' from a gacha!',
"color" => 4437377
];
}
else
{
$embed = [
"title" => $data['event']['avatars']['purchaser']['name'] . ' bought a ' . $data['event']['product']['productName'],
"color" => 7506394
];
}
}
}
$embed['image'] = [
"url" => "https://caspervend.casperdns.com/img.php?u=".$data['event']['product']['texture']."&g=SLIFE"
];
$embed['description'] = '**Paid:** L$' . $data['event']['money']['gross']. "\n".
'**Received:** L$' . $data['event']['money']['received']. "\n".
'**Location:** ' . $data['event']['vendor']['location']. "\n";
if ($data['event']['avatars']['recipient']['uuid'] !== $data['event']['avatars']['purchaser']['uuid'])
{
$embed['description'] = "**As a gift for " . $data['event']['avatars']['recipient']['name'] . "**\n" . $embed['description'];
}
if ($data['event']['flags']['giftCard'] === true || $data['event']['flags']['giftCardV3'] === true) {
$embed['description'] .= "*This was a gift card purchase*\n";
}
}
else if ($eventType === 'marketplace_sale')
{
$embed = [
"title" => $data['event']['PayerName'] . ' bought a ' . $data['event']['ItemName'] . ' from the Marketplace',
"color" => 814798,
"description" => "**Paid** L$" .$data['event']['PaymentGross']. "\n**Fee** L$" . $data['event']['PaymentFee']
];
if ($data['event']['ReceiverKey'] !== $data['event']['PayerKey'])
{
$embed['description'] = "**As a gift for " . $data['event']['ReceiverNAme'] . "**\n" . $embed['description'];
}
}
else
{
$embed = [
"title" => 'Received unsupported event of type ' . $eventType,
"color" => 7506394
];
}
$json_data = json_encode([
"username" => "Store",
"embeds" => [
$embed
]
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
$ch = curl_init( DISCORD_WEBHOOK_URL );
curl_setopt( $ch, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_setopt( $ch, CURLOPT_POST, 1);
curl_setopt( $ch, CURLOPT_POSTFIELDS, $json_data);
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt( $ch, CURLOPT_HEADER, 0);
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec( $ch );
if (ENABLE_DEBUG)
{
mail(DEBUG_EMAIL, 'Discord Webhook Response', $response);
}
curl_close( $ch );