Webhooks allow games to receive real-time notifications about various events occurring within the project, such as asset mints, transfers, marketplace listings, and more. This guide provides detailed information on how to create, manage, and verify webhooks using the provided UI and API endpoints.

Creating Webhooks via UI

Gameshift provides a user-friendly UI for creating and managing webhooks. To create a new webhook:

  1. Navigate to the "Webhooks" page within your game's settings.
  2. Click on the "+ New Webhook" button.
  3. Fill in the following details:
    • Webhook Name (optional): A descriptive name for the webhook.
    • Webhook URL: The URL where event notifications will be sent.
    • Event Types: Select the desired event types to receive notifications for.
    • Environments: Choose the environments (e.g., Development, Production) in which the webhook should be active.
  4. Click "Save Changes" to create the webhook.

Webhook Events and Payloads

The following webhook events are available, along with their corresponding payload structures:

Asset Events

  • asset.mint.initiated
  • asset.mint.completed
  • asset.mint.failed
  • asset.transfer.initiated
  • asset.transfer.completed
  • asset.transfer.failed
  • asset.marketplace.listed
  • asset.marketplace.unlisted
  • asset.marketplace.buy

Asset event payloads include:

type AssetEventCommonPayload = {
  type: 'asset';
  assetId: string | null;
  collectionId: string;
  details?: any;
  transactionId?: string;
};

Collection Events

  • collection.mint.initiated
  • collection.mint.completed
  • collection.mint.failed

Collection event payloads include:

type CollectionEventCommonPayload = {
  type: 'collection';
  collectionId: string;
  details: any;
};

Withdrawal Events

  • withdrawal.initiated
  • withdrawal.completed
  • withdrawal.failed

Withdrawal event payloads include:

type WithdrawalEventPayload = {
  type: 'withdrawal';
  status: 'success' | 'failed';
  amount: number | Decimal | null | undefined;
  from?: string;
  to?: string;
  transactionId?: string;
};

Payment Events

  • payment.initiated
  • payment.completed
  • payment.failed

Payment event payloads include:

type PaymentEventPayload = {
  type: 'payment';
  status: 'initiated' | 'success' | 'failed';
  currency: 'USD' | 'USDC' | 'SOL';
  unit: 'cents' | 'dollars' | 'lamports';
  amount: number;
  paymentId: string;
};

Transaction Events

  • transaction.update

Transaction event payloads include:

type TransactionEventPayload = {
  type: 'transaction';
  status: 'initiated' | 'completed' | 'failed';
  environment: string;
  transactionId: string;
  txHash: string;
};

Verifying Webhooks

To ensure the authenticity and integrity of webhook events received by your application, Gameshift includes a signature verification process. Each webhook event includes a signature in the webhook-signature header, which is generated using the webhook's secret key.

Managing Webhook Secrets

Webhook secrets are used to sign and verify webhook events. Each webhook has its own unique secret key, which can be generated and managed through the Gameshift UI or API.

Remember to keep your webhook secrets secure and never share them publicly. If you suspect that a webhook secret has been compromised, generate a new secret immediately to maintain the security of your webhooks.

Generating a New Webhook Secret

To generate a new webhook secret:

  1. Navigate to the "Webhooks" page within your game's settings.
  2. Click on the "Generate Key" button next to the "Webhook Validation Key" section.
  3. A confirmation dialog will appear. Click "Confirm" to proceed with generating a new secret key.
  4. The new webhook secret will be displayed on the screen. Make sure to copy and store it securely, as it will not be shown again.

Note: Generating a new webhook secret will invalidate the previous secret, and all events signed with the old secret will fail verification.

Retrieving the Webhook Secret

To retrieve the current webhook secret:

  1. Navigate to the "Webhooks" page within your game's settings.
  2. The webhook secret will be displayed in the "Webhook Validation Key" section, partially masked for security purposes.

Note: the complete webhook secret is not available after the fist time it was generated for security purposes, you should securely store it after generating it.

Verifying Webhook Signatures

By following the verification process and properly managing webhook secrets, you can ensure that the received webhook events are genuine and have not been tampered with.

To verify the signature of a received webhook event:

  1. Retrieve the webhook-id, webhook-timestamp, and webhook-signature from the request headers.
  2. Concatenate the webhook-id, webhook-timestamp, and the stringified request data, the data exists in the data field of the request.body, separated by periods (.). For example: <webhook-id>.<webhook-timestamp>.<request-body>.
  3. Create an HMAC SHA256 signature using the webhook's secret key in bytes and the concatenated string from step 2.
  4. Compare the generated signature with the value of the webhook-signature header. If they match, the webhook event is verified.

📘

Note:

The webhook data which is passed into the verification comes the data field within the request body, it is not the entire request body itself. webhookData = req.body.data. The svix webhook tool can be used to see if the verification process is valid. https://www.standardwebhooks.com/verify/svix

Below is an example code snippet for verifying webhook events in a few popular languages:

const crypto = require('crypto');

function verifyWebhook(
  webhookId,
  webhookTimestamp,
  webhookSignature,
  webhookData, // comes from req.body.data
  secretKey
) {
  const webhookDataStr = JSON.stringify(webhookData);
  const signedContent = `${webhookId}.${webhookTimestamp}.${webhookDataStr}`;
  const secretBytes = Buffer.from(secretKey, 'base64');
  const expectedSignature = crypto
    .createHmac('sha256', secretBytes)
    .update(signedContent)
    .digest('base64');
  
  return `v1,${expectedSignature}` === webhookSignature;
}
using System;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;

public class WebhookVerifier
{
  public static bool VerifyWebhook(
    string webhookId,
    string webhookTimestamp,
    string webhookSignature,
    object webhookData,
    string secretKey)
  {
    string webhookDataStr = JsonConvert.SerializeObject(webhookData);
    string signedContent = $"{webhookId}.{webhookTimestamp}.{webhookDataStr}";
    byte[] secretBytes = Convert.FromBase64String(secretKey);

    using (var hmac = new HMACSHA256(secretBytes))
    {
      byte[] signatureBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(signedContent));
      string expectedSignature = Convert.ToBase64String(signatureBytes);
      return $"v1,{expectedSignature}" == webhookSignature;
    }
  }
}
import json
import hmac
import base64

def verify_webhook(
    webhook_id,
    webhook_timestamp,
    webhook_signature,
    webhook_data,  # comes from request.json() or similar
    secret_key
):
    payload_string = json.dumps(webhook_data)
    signed_content = f"{webhook_id}.{webhook_timestamp}.{payload_string}"
    secret_bytes = base64.b64decode(secret_key)
    
    expected_signature = base64.b64encode(
        hmac.new(secret_bytes, signed_content.encode('utf-8'), digestmod='sha256').digest()
    ).decode('utf-8')
    
    return f"v1,{expected_signature}" == webhook_signature
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import com.fasterxml.jackson.databind.ObjectMapper;

public class WebhookVerifier {
  public static boolean verifyWebhook(
      String webhookId,
      String webhookTimestamp,
      String webhookSignature,
      Object webhookData,
      String secretKey) throws Exception {
    ObjectMapper objectMapper = new ObjectMapper();
    String webhookDataStr = objectMapper.writeValueAsString(webhookData);
    String signedContent = webhookId + "." + webhookTimestamp + "." + webhookDataStr;
    byte[] secretBytes = Base64.getDecoder().decode(secretKey);

    Mac sha256Hmac = Mac.getInstance("HmacSHA256");
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretBytes, "HmacSHA256");
    sha256Hmac.init(secretKeySpec);

    byte[] signatureBytes = sha256Hmac.doFinal(signedContent.getBytes(StandardCharsets.UTF8));
    String expectedSignature = Base64.getEncoder().encodeToString(signatureBytes);

    return ("v1," + expectedSignature).equals(webhookSignature);
  }
}

Make sure to replace secretKey with the actual webhook secret you initially copied from the dashboard.