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: 'success' | 'failed';
  currency: 'USD' | 'USDC' | 'SOL';
  unit: 'cents' | 'dollars' | 'lamports';
  amount: number;
  transactionId?: 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 body, separated by periods (.). For example: <webhook-id>.<webhook-timestamp>.<request-body>.
  3. Create an HMAC SHA256 signature using the webhook's secret key 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.

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

const crypto = require('crypto');

function verifyWebhook(webhookId, timestamp, signature, payload, secretKey) {
  const signedContent = `${webhookId}.${timestamp}.${JSON.stringify(payload)}`;
  const expectedSignature = crypto.createHmac('sha256', secretKey)
    .update(signedContent)
    .digest('base64');

  return expectedSignature === signature;
}
using System;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;

public static class WebhookVerification
{
    public static bool VerifyWebhook(string webhookId, long timestamp, string signature, object payload, string secretKey)
    {
        string signedContent = $"{webhookId}.{timestamp}.{JsonConvert.SerializeObject(payload)}";

        using (HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey)))
        {
            byte[] signatureBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(signedContent));
            string expectedSignature = Convert.ToBase64String(signatureBytes);

            return expectedSignature == signature;
        }
    }
}
import hashlib
import hmac
import base64
import json

def verify_webhook(webhook_id, timestamp, signature, payload, secret_key):
    signed_content = f"{webhook_id}.{timestamp}.{json.dumps(payload)}"

    hmac_obj = hmac.new(secret_key.encode(), msg=signed_content.encode(), digestmod=hashlib.sha256)
    expected_signature = base64.b64encode(hmac_obj.digest()).decode()

    return expected_signature == signature
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import com.fasterxml.jackson.databind.ObjectMapper;

public class WebhookVerification {
    public static boolean verifyWebhook(String webhookId, long timestamp, String signature, Object payload, String secretKey) {
        try {
            String signedContent = webhookId + "." + timestamp + "." + new ObjectMapper().writeValueAsString(payload);

            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
            mac.init(secretKeySpec);

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

            return expectedSignature.equals(signature);
        } catch (InvalidKeyException | NoSuchAlgorithmException | com.fasterxml.jackson.core.JsonProcessingException e) {
            e.printStackTrace();
            return false;
        }
    }
}

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