HandCash provides webhook notifications for all activities related to your application’s items, allowing you to stay synchronized with blockchain events that occur outside your game or application.

Webhook Types

There are three types of webhooks available:

  1. Item Listing Payment Completed
  2. Items Transferred
  3. Item Creation Order Completed

Configure these webhooks in the Developer Dashboard.

Webhook Authentication

The signature is passed in the handcash-signature header of the webhook request.

To ensure the authenticity of webhook calls, implement the following authentication process:

import crypto from 'crypto';

function getWebhookEvent(signature: string, body: any, appSecret: string): WebhookPayload {
  if (!signature) {
    throw new Error('No signature provided');
  }

  const fiveMinutesAgo = new Date(new Date().getTime() - 5 * 60000);
  if (new Date(body.created) < fiveMinutesAgo) {
    throw new Error('Timestamp is too old');
  }

  const hmac = crypto.createHmac('sha256', appSecret);
  hmac.update(JSON.stringify(body));
  const generatedSignature = hmac.digest('hex');

  if (generatedSignature !== signature) {
    throw new Error('Invalid signature');
  }

  return body as WebhookPayload;
}

Webhook Payload Structure

All webhooks share a common base structure:

interface WebhookPayload {
  event: 'item_listing_payment_completed' | 'items_transferred' | 'item_creation_order_completed';
  applicationId: string;
  apiVersion: string;
  created: string;
}

Item Listing Payment Completed

Triggered when a payment for a listed item is completed.

interface ItemListingPaymentCompletedEventPayload extends WebhookPayload {
  event: 'item_listing_payment_completed';
  data: {
    itemTransfer: ItemTransfer;
  };
}

Items Transferred

Triggered when items are transferred between users.

interface ItemsTransferredEventPayload extends WebhookPayload {
  event: 'items_transferred';
  data: {
    itemTransfer: ItemTransfer;
  };
}

Item Creation Order Completed

Triggered when an item creation order is completed.

interface ItemCreationEventPayload extends WebhookPayload {
  event: 'item_creation_order_completed';
  data: {
    items: Item[];
    itemCreationOrder: CreateItemsOrder;
  };
}

Key Types

ItemTransfer

type ItemTransfer = {
  id: string;
  label: 'send' | 'receive' | 'marketBuy' | 'marketSell' | 'marketCancel';
  payment?: ItemPayment;
  items: SingleItemTransfer[];
  createdAt: string;
};

Item

type Item = {
  id: string;
  origin: string;
  name: string;
  description: string;
  imageUrl: string;
  multimediaUrl: string;
  multimediaType: string;
  rarity: string;
  color: string;
  attributes: ItemAttribute[];
  externalId?: string;
  collection: {
    id: string;
    description: string;
    app: {
      id: string;
      name: string;
      iconUrl: string;
    };
    origin: string;
    name: string;
    imageUrl: string;
  };
  user: {
    alias: string;
    displayName: string;
    profilePictureUrl: string;
  };
  app: {
    id: string;
    name: string;
    iconUrl: string;
  };
  itemListing: {
    id: string;
    status: string;
    currencyCode: string;
    price: number;
    denominatedIn: string;
    paymentRequestUrl: string;
    paymentRequestId: string;
    fiatEquivalent: {
      currencyCode: string;
      amount: number;
    };
  };
};

CreateItemsOrder

type CreateItemsOrder = {
  id: string;
  type: 'collectionItem' | 'collection';
  status: 'preparing' | 'pendingPayment' | 'pendingInscriptions' | 'completed';
  collectionOrdinalId?: string;
  items: CreateItemMetadata[];
  payment?: {
    paymentRequestId: string;
    paymentRequestUrl: string;
    amountInUSD: number;
    transactionId: string;
    isConfirmed: boolean;
  };
  pendingInscriptions?: number;
  error?: string;
  uid?: string;
};

Handling Webhooks

  1. Set up an endpoint in your application to receive webhook POST requests.
  2. Verify the webhook signature using the authentication process above.
  3. Parse the webhook payload based on the event type.
  4. Update your application’s state or trigger relevant actions based on the webhook data.

Example webhook handler:

app.post('/webhooks/handcash-items', (req, res) => {
  try {
    const signature = req.headers['handcash-signature'] as string;
    const event = getWebhookEvent(signature, req.body, process.env.APP_SECRET);

    switch (event.event) {
      case 'item_listing_payment_completed':
        handleItemSold(event as ItemListingPaymentCompletedEventPayload);
        break;
      case 'items_transferred':
        handleItemTransfer(event as ItemsTransferredEventPayload);
        break;
      case 'item_creation_order_completed':
        handleItemCreation(event as ItemCreationEventPayload);
        break;
    }

    res.sendStatus(200);
  } catch (error) {
    console.error('Webhook error:', error);
    res.sendStatus(400);
  }
});

Best Practices

  1. Always verify the webhook signature to ensure the request is from HandCash.
  2. Use the handcash-signature header to retrieve the signature for verification.
  3. Implement idempotency to handle potential duplicate webhook deliveries.
  4. Process webhooks asynchronously to avoid blocking your application.
  5. Store raw webhook data for debugging and auditing purposes.
  6. Implement proper error handling and logging for webhook processing.

Signature Generation

For reference, HandCash generates the signature using the following method:

static getAppSecretSignature(appSecret: string, body: any): string {
  const hmac = crypto.createHmac('sha256', appSecret);
  hmac.update(JSON.stringify(body));
  return hmac.digest('hex');
}

By leveraging these webhooks, your application can stay up-to-date with all item-related activities, ensuring a seamless experience for your users across the HandCash ecosystem and your application.