TANITARDevelopers
WebhooksEvent catalogShop

shop.listing.published

Version 1 of the shop.listing.published webhook event.

The published shape of a Shop listing changed: the listing entered the published state, or an already-published listing was edited - including a scheduled discount change - and carries a new monotonic revision. The payload is the whole published shape rather than a delta, which is why no dedicated discount event exists. Draft-state edits publish nothing. The stock policy is disclosed; the code pool behind a code-pool policy is not.

Event typeshop.listing.published
Schema version1
Routing keyevent.shop.listing.published.v1
Produced byReward Management
Schemaurn:defendr:contract:shop:listing-published:1

data fields

FieldTypeRequiredDescription
listingKeystringyes
revisionintegeryes
titlestringyes
categorystring or nullyes
groupKeystring or nullyes
groupOrderinteger or nullyesPresentational order within groupKey; null when the listing renders alone.
pricearray of objectyesThe listing's undiscounted price, one leg per accepted currency.
price[].currencyKeystringyes
price[].amountstringyesA whole amount of the tenant's published currency, as a decimal string.
discountobject or nullyes
discount.kind"percentage", "absolute"yes
discount.percentagestring or nullyesThe percentage taken off, as a decimal string between 0 and 100; null for an absolute discount.
discount.absolutearray or nullyesThe amount taken off per currency leg; null for a percentage discount.
discount.absolute[].currencyKeystringyes
discount.absolute[].amountstringyesA whole amount of the tenant's published currency, as a decimal string.
discount.startsAtstring (date-time)yes
discount.endsAtstring or null (date-time)yesThe end of the discount window; null for an open-ended discount.
fulfillmentobjectyes
fulfillment.kind"award", "code", "manual"yes
fulfillment.awardKeystring or nullyes
stockobjectyes
stock.policy"unlimited", "counted", "code-pool"yes
stock.remaininginteger or nullyesUnits still buyable; for a code-pool policy this is the count of unclaimed codes. Null only for an unlimited policy.
limitobject or nullyes
limit.maxPerPlayerintegeryes
limit.window"day", "week", "total"yes
actorIdstringyes
publishedAtstring (date-time)yes

Some fields are required only in particular states. The JSON Schema below is authoritative.

Example delivery

Signed with the example secret whsec_placeholder_example_webhooks_signing_secret, so you can check your verifier against it. See Verifying signatures.

POST /your/webhook/endpoint HTTP/1.1
content-type: application/json
webhook-id: whd_00000000-0000-4000-8000-000000000027
webhook-timestamp: 1800000000
webhook-signature: v1,Zottz1KBjAGBJAi8WGTYYtEfKG9p/f/p7+6j3SgKNpA=
tanitar-event-type: shop.listing.published
tanitar-schema-version: 1
tanitar-delivery-attempt: 1

{"messageId":"evt_00000000-0000-4000-8000-000000000027","kind":"event","type":"shop.listing.published","schemaVersion":1,"occurredAt":"2027-01-15T10:00:00.000Z","producer":"reward-management-service","accountId":"acc_11111111-1111-4111-8111-111111111111","correlationId":"corr_00000000-0000-4000-8000-000000000027","data":{"listingKey":"founder-badge","revision":3,"title":"Founder badge","category":"cosmetics","groupKey":"founder","groupOrder":1,"price":[{"currencyKey":"red","amount":"500"}],"discount":{"kind":"percentage","percentage":"20","absolute":null,"startsAt":"2026-09-27T00:00:00.000Z","endsAt":"2026-10-04T00:00:00.000Z"},"fulfillment":{"kind":"award","awardKey":"founder"},"stock":{"policy":"unlimited","remaining":null},"limit":{"maxPerPlayer":1,"window":"total"},"actorId":"operator_demo","publishedAt":"2026-09-27T00:00:00.000Z"}}

The same body, formatted:

{
  "messageId": "evt_00000000-0000-4000-8000-000000000027",
  "kind": "event",
  "type": "shop.listing.published",
  "schemaVersion": 1,
  "occurredAt": "2027-01-15T10:00:00.000Z",
  "producer": "reward-management-service",
  "accountId": "acc_11111111-1111-4111-8111-111111111111",
  "correlationId": "corr_00000000-0000-4000-8000-000000000027",
  "data": {
    "listingKey": "founder-badge",
    "revision": 3,
    "title": "Founder badge",
    "category": "cosmetics",
    "groupKey": "founder",
    "groupOrder": 1,
    "price": [
      {
        "currencyKey": "red",
        "amount": "500"
      }
    ],
    "discount": {
      "kind": "percentage",
      "percentage": "20",
      "absolute": null,
      "startsAt": "2026-09-27T00:00:00.000Z",
      "endsAt": "2026-10-04T00:00:00.000Z"
    },
    "fulfillment": {
      "kind": "award",
      "awardKey": "founder"
    },
    "stock": {
      "policy": "unlimited",
      "remaining": null
    },
    "limit": {
      "maxPerPlayer": 1,
      "window": "total"
    },
    "actorId": "operator_demo",
    "publishedAt": "2026-09-27T00:00:00.000Z"
  }
}

JSON Schema

urn:defendr:contract:shop:listing-published:1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:defendr:contract:shop:listing-published:1",
  "title": "Shop listing published",
  "description": "The published shape of a Shop listing changed: the listing entered the `published` state, or an already-published listing was edited - including a scheduled discount change - and carries a new monotonic revision. The payload is the whole published shape rather than a delta, which is why no dedicated discount event exists. Draft-state edits publish nothing. The stock policy is disclosed; the code pool behind a `code-pool` policy is not.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "listingKey",
    "revision",
    "title",
    "category",
    "groupKey",
    "groupOrder",
    "price",
    "discount",
    "fulfillment",
    "stock",
    "limit",
    "actorId",
    "publishedAt"
  ],
  "properties": {
    "listingKey": {
      "type": "string",
      "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
    },
    "revision": {
      "type": "integer",
      "minimum": 1
    },
    "title": {
      "type": "string",
      "minLength": 1
    },
    "category": {
      "type": [
        "string",
        "null"
      ]
    },
    "groupKey": {
      "type": [
        "string",
        "null"
      ],
      "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
    },
    "groupOrder": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": 0,
      "description": "Presentational order within `groupKey`; null when the listing renders alone."
    },
    "price": {
      "type": "array",
      "minItems": 1,
      "description": "The listing's undiscounted price, one leg per accepted currency.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "currencyKey",
          "amount"
        ],
        "properties": {
          "currencyKey": {
            "type": "string",
            "pattern": "^[a-z][a-z0-9-]*$"
          },
          "amount": {
            "type": "string",
            "pattern": "^[0-9]+$",
            "description": "A whole amount of the tenant's published currency, as a decimal string."
          }
        }
      }
    },
    "discount": {
      "type": [
        "object",
        "null"
      ],
      "additionalProperties": false,
      "required": [
        "kind",
        "percentage",
        "absolute",
        "startsAt",
        "endsAt"
      ],
      "properties": {
        "kind": {
          "enum": [
            "percentage",
            "absolute"
          ]
        },
        "percentage": {
          "type": [
            "string",
            "null"
          ],
          "pattern": "^(?:100(?:\\.0{1,4})?|[0-9]{1,2}(?:\\.[0-9]{1,4})?)$",
          "description": "The percentage taken off, as a decimal string between 0 and 100; null for an absolute discount."
        },
        "absolute": {
          "type": [
            "array",
            "null"
          ],
          "description": "The amount taken off per currency leg; null for a percentage discount.",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "currencyKey",
              "amount"
            ],
            "properties": {
              "currencyKey": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9-]*$"
              },
              "amount": {
                "type": "string",
                "pattern": "^[0-9]+$",
                "description": "A whole amount of the tenant's published currency, as a decimal string."
              }
            }
          }
        },
        "startsAt": {
          "type": "string",
          "format": "date-time"
        },
        "endsAt": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time",
          "description": "The end of the discount window; null for an open-ended discount."
        }
      },
      "allOf": [
        {
          "if": {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "const": "percentage"
              }
            }
          },
          "then": {
            "type": "object",
            "properties": {
              "percentage": {
                "type": "string"
              },
              "absolute": {
                "type": "null"
              }
            }
          }
        },
        {
          "if": {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "const": "absolute"
              }
            }
          },
          "then": {
            "type": "object",
            "properties": {
              "percentage": {
                "type": "null"
              },
              "absolute": {
                "type": "array"
              }
            }
          }
        }
      ]
    },
    "fulfillment": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "kind",
        "awardKey"
      ],
      "properties": {
        "kind": {
          "enum": [
            "award",
            "code",
            "manual"
          ]
        },
        "awardKey": {
          "type": [
            "string",
            "null"
          ],
          "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
        }
      }
    },
    "stock": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "policy",
        "remaining"
      ],
      "properties": {
        "policy": {
          "enum": [
            "unlimited",
            "counted",
            "code-pool"
          ]
        },
        "remaining": {
          "type": [
            "integer",
            "null"
          ],
          "minimum": 0,
          "description": "Units still buyable; for a `code-pool` policy this is the count of unclaimed codes. Null only for an `unlimited` policy."
        }
      }
    },
    "limit": {
      "type": [
        "object",
        "null"
      ],
      "additionalProperties": false,
      "required": [
        "maxPerPlayer",
        "window"
      ],
      "properties": {
        "maxPerPlayer": {
          "type": "integer",
          "minimum": 1
        },
        "window": {
          "enum": [
            "day",
            "week",
            "total"
          ]
        }
      }
    },
    "actorId": {
      "type": "string",
      "minLength": 1
    },
    "publishedAt": {
      "type": "string",
      "format": "date-time"
    }
  },
  "allOf": [
    {
      "if": {
        "type": "object",
        "required": [
          "fulfillment"
        ],
        "properties": {
          "fulfillment": {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "const": "code"
              }
            }
          }
        }
      },
      "then": {
        "type": "object",
        "properties": {
          "fulfillment": {
            "type": "object",
            "properties": {
              "awardKey": {
                "type": "null"
              }
            }
          },
          "stock": {
            "type": "object",
            "properties": {
              "policy": {
                "const": "code-pool"
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "type": "object",
        "required": [
          "fulfillment"
        ],
        "properties": {
          "fulfillment": {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "const": "award"
              }
            }
          }
        }
      },
      "then": {
        "type": "object",
        "properties": {
          "fulfillment": {
            "type": "object",
            "properties": {
              "awardKey": {
                "type": "string"
              }
            }
          },
          "stock": {
            "type": "object",
            "properties": {
              "policy": {
                "enum": [
                  "unlimited",
                  "counted"
                ]
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "type": "object",
        "required": [
          "fulfillment"
        ],
        "properties": {
          "fulfillment": {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "const": "manual"
              }
            }
          }
        }
      },
      "then": {
        "type": "object",
        "properties": {
          "fulfillment": {
            "type": "object",
            "properties": {
              "awardKey": {
                "type": "null"
              }
            }
          },
          "stock": {
            "type": "object",
            "properties": {
              "policy": {
                "enum": [
                  "unlimited",
                  "counted"
                ]
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "type": "object",
        "required": [
          "stock"
        ],
        "properties": {
          "stock": {
            "type": "object",
            "required": [
              "policy"
            ],
            "properties": {
              "policy": {
                "const": "unlimited"
              }
            }
          }
        }
      },
      "then": {
        "type": "object",
        "properties": {
          "stock": {
            "type": "object",
            "properties": {
              "remaining": {
                "type": "null"
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "type": "object",
        "required": [
          "stock"
        ],
        "properties": {
          "stock": {
            "type": "object",
            "required": [
              "policy"
            ],
            "properties": {
              "policy": {
                "enum": [
                  "counted",
                  "code-pool"
                ]
              }
            }
          }
        }
      },
      "then": {
        "type": "object",
        "properties": {
          "stock": {
            "type": "object",
            "properties": {
              "remaining": {
                "type": "integer"
              }
            }
          }
        }
      }
    }
  ]
}

On this page