Webhooks

FreeStuff's webhooks are POST requests. Each request is refered to as a 'Message' below. The messages are delivered to the url you have configured on your dashboard. All data is in the shape of the compatibility date defined on the dashboard.

Message Payload

The message payload (http body) follows the Standard Webhooks Spec and looks as follows:

{
  "type": "fsb:event:announcement_created",
  "timestamp": "2022-11-03T20:26:10.344522Z",
  "data": {
    // ...
  }
}

See Event List for a list of events.

Message Headers

The webhook request will have the following headers:

Verifying the request

FreeStuff signs every webhook request as per Standard Webhooks Spec. While not required, it is highly recommended you check for a valid signature before processing any events received.

  • You can find your Ed25519 public key on your app page
  • The content to be verified is msg_id.timestamp.payload with msg_id and timestamp being provided as Webhook-Id and Webhook-Timestamp headers respectively
  • Make sure to take the raw http body (prior to parsing the JSON content) for the verification payload
  • Make sure to store the msg_id for a certain amount of time to prevent package duplication and replay attacks
  • Make sure to ignore requests with an old Webhook-Timestamp value
  • Make sure to use the payload body timestamp field as the contextual timestamp and use the header only for verification
If you are not using a library to verify the request for you and you are not interested in implementing the verification yourself, we recommend you pick a long and unguessable url for your webhook to prevent abuse.

Resends

FreeStuff attempts to resend failed deliveries as per Standard Webhooks Spec. You are required to respond with a 2xx status code after successfully receiving the data.

  • Failure to process the data or perform actions with the data on your end should not yield a non-2xx status code as the message was delivered successfully
  • While the Standard Webhooks Spec suggests a schedule for resends, FreeStuff may use a different timing for when resends are scheduled
  • If you fail to receive multiple events in a row (no 2xx codes returned in multiple consecutive events spanning multiple days) your webhook will be considered dead and will be removed from our system. This will not revoke your api access entirely but you will have to configure your webhook again.

Responding to webhook requests

  • A code of 204 No Content with an empty body is prefered as the server will ignore your response body anyway
  • If for any reason you cannot send a 204, any other 2xx code is accepted as well
  • You may send a X-Set-Compatibility-Date header with a compatibility date on your response
    • If the value is a valid compatibility date the setting in your app will be overwritten to use this compatibility date for all subsequent deliveries
    • The new compatibility date already applies to resends so you can respond with a 4xx code if you read an unexpected X-Compatibility-Date request header and force FreeStuff to deliver the message again with the correct date
    • Library authors are encouraged to set this header on every response to the newest compatibility date their library is compatible with, as well as to 4xx every request with an incompatible X-Compatibility-Date request header. This way users can easily install and update your library without manual configuration on the dashboard or even knowing about the compatibility date system
    • If you respond with a non 2xx status while having an invalid value for your X-Set-Compatibility-Date, the message will not be scheduled for a resend as it is assumed you let the request fail because of the compatibility date missmatch while expecting a non-valid date
  • You may send a X-Client-Library header with details about your library
    • Library authors are encouraged to set this header on every response
    • The format should be Library Name/version (url to library homepage or git repo) - e.g. FreeStuff.py/1.0.2 (https://github.com/my/fake-repo)

Event List

fsb:event:ping

Fired either when the ping button was pressed on your dashboard or periodically to check if your webhook is still operational.

The data field will contain the following object:

{
  "manual": boolean // will be true if you manually requested this ping, false otherwise
}

fsb:event:announcement_created

Fired when a new announcement is published.

The data field will contain a single ResolvedAnnouncement. Check it's resolvedProducts field for a list of all new products.

ResolvedAnnouncement: ObjectData Model
{
id: number
products: int[] ids of all products in the announcement
resolvedProducts: Product[] resolved products
}

fsb:event:product_updated

Fired when a previously published product is updated.

The data field will contain the full Product data, not just the changes.

Product: ObjectData Model
{
id: int
title: string
prices: ProductPrice[]
kind: ProductKind
tags: string[] unsorted, non-standardized, human readable
images: ProductImage[] *this array will only have a subset of all available images on the free tier
description: string human readable description in english language
rating: float this is an aggregated score where 0 represents the worst possible and 1 the best possible rating
copyright: string name of the ip copyright holder
until: int unix timestamp in ms
type: PublishingChannel
urls: ProductUrl[] *this array will only have a subset of all available urls on the free tier
store: Store steam, epic games, etc.
flags: ProductFlags
notice: string a rare optional extra info text added by a FreeStuff team member meant for public display alongside the product
staffApproved: boolean true means a human checked the data prior to being published
*platforms: Platform[] *this array will always be empty on the free tier
*meta: ProductMeta[] *this array will always be empty on the free tier
}
The Data Models above show compatibility date 2025-03-01. Newer versions might differ in details.