> For the complete documentation index, see [llms.txt](https://docs.startrail.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.startrail.io/issue-transfer-api/issue-and-transfer-srr-nft/webhook-setup.md).

# Webhook Setup

## Overview

Startbahn delivers integration events from your Licensed User Wallet (LUW) to an HTTPS endpoint you control. Each event is sent as a JSON `POST` request and is retried on failure.

You can use webhooks to react to on-chain activity in near real-time — for example, persisting newly issued SRRs, advancing a transfer flow once the on-chain reservation is confirmed, or updating internal state when a collection contract finishes deploying.

{% hint style="info" %}
Webhook subscriptions are not self-service today. The Startbahn team will configure your endpoint, API key, and the set of events you want to receive based on the [Subscribing to webhooks](#subscribing-to-webhooks) information you provide.
{% endhint %}

## Events at a glance

| Subscription name             | Payload `type`             | Fires when                                                 |
| ----------------------------- | -------------------------- | ---------------------------------------------------------- |
| `issueComplete`               | `issue_complete`           | One or more SRRs have been minted.                         |
| `transferReservationComplete` | `transfer_key`             | A transfer has been reserved on-chain (transfer key flow). |
| `transferExecutionComplete`   | `transfer_complete`        | A transfer has executed on-chain and the owner changed.    |
| `collectionCreated`           | `collection_created`       | A collection contract has been deployed.                   |
| `collectionCreateFailed`      | `collection_create_failed` | A collection-creation transaction failed to be mined.      |

## Delivery model

### Request format

Each delivery is an HTTP `POST` to your configured URL with a JSON body and the following headers:

```
Content-Type: application/json
x-api-key: <the API key you provided when subscribing>
```

Use the `x-api-key` header to authenticate the request on your side. Treat the key as a shared secret.

### Expected response

Respond with any `2xx` status to acknowledge receipt. Your endpoint should respond quickly; long-running work should be queued on your side and processed asynchronously.

### Retries and cancellation

If your endpoint returns a non-`2xx` status, times out, or is unreachable, the delivery is retried. You can configure the total number of attempts (between `1` and `5`, default `1`).

Once every attempt fails, Startbahn:

1. Marks the delivery as cancelled and stops retrying it.
2. Sends an informational email (in CSV format) to the contact email you provided, containing the same data that the webhook would have delivered.

Cancellations are per-delivery — subsequent events are still attempted independently.

### Batching and `groupId`

When multiple events of the same `type` are destined for the same URL and API key, they may be combined into a single delivery whose `data` array contains all of them.

Each entry in `data` carries a `groupId` so you can correlate or deduplicate entries:

* For SRR-related events, `groupId` is the SRR's token ID.
* For collection-related events, `groupId` is `${ownerAddress}${name}` — i.e. the LUW address concatenated with the collection name.

## Common payload structure

Every webhook body shares the same envelope:

```json
{
  "type": "<event payload type>",
  "version": <integer>,
  "data": [ /* one or more entries */ ]
}
```

<mark style="color:red;">`*`</mark> indicates the field is always present.

<table><thead><tr><th width="240">Field</th><th width="140">Format</th><th>Description</th></tr></thead><tbody><tr><td><code>type</code><mark style="color:red;">*</mark></td><td>enum</td><td>One of <code>issue_complete</code>, <code>transfer_key</code>, <code>transfer_complete</code>, <code>collection_created</code>, <code>collection_create_failed</code>.</td></tr><tr><td><code>version</code><mark style="color:red;">*</mark></td><td>integer</td><td>Payload schema version. SRR events are currently <code>2</code>; collection events are currently <code>1</code>.</td></tr><tr><td><code>data</code><mark style="color:red;">*</mark></td><td>array</td><td>One or more entries. The shape of each entry depends on <code>type</code>.</td></tr><tr><td><code>data[*].groupId</code><mark style="color:red;">*</mark></td><td>string</td><td>Identifier used to group related entries inside a single delivery. See <a href="#batching-and-groupid">Batching and <code>groupId</code></a>.</td></tr></tbody></table>

{% hint style="warning" %}
**Forward compatibility:** We may add new fields to a payload without bumping `version`. Make sure your parser ignores unknown fields rather than rejecting them. Removals will be announced and deprecated first.
{% endhint %}

## SRR webhook events

The three SRR events (`issueComplete`, `transferReservationComplete`, `transferExecutionComplete`) share a common set of fields in addition to the envelope above.

### Fields common to all SRR events

<table><thead><tr><th width="260">Field</th><th width="140">Format</th><th>Description</th></tr></thead><tbody><tr><td><code>data[*].srrId</code><mark style="color:red;">*</mark></td><td>string</td><td>The SRR's token ID.</td></tr><tr><td><code>data[*].collectionContractAddress</code><mark style="color:red;">*</mark></td><td>string | null</td><td>Address of the collection contract the SRR lives on. <code>null</code> means the SRR is on the default collection. Added in payload <code>version: 2</code>.</td></tr><tr><td><code>data[*].metadata</code></td><td>object</td><td>Raw SRR metadata JSON. See <a href="/pages/hx1j9Oc1YPYQy3C93RIm">Metadata Schema</a> for the shape.</td></tr></tbody></table>

{% hint style="info" %}
**`srrId` is not globally unique** — two SRRs on two different collections can share the same token ID. Treat `(collectionContractAddress, srrId)` as the unique identifier when persisting or looking up SRRs on your side.
{% endhint %}

{% hint style="warning" %}
**`data[*].metadata` shape**

`data[*].metadata` is the **raw SRR metadata JSON** — the same object you can fetch from the [Startrail IPFS CDN gateway](/subgraph/ipfs-cdn-gateway.md) at `https://cdn.startrail.io/ipfs/<cid>`. It is not wrapped in a `{ digest, json, createdAt, updatedAt, cid }` envelope.

The CID is conveyed separately:

* On `issueComplete`, via `data[*].srrCid` (when available).
* On `transferReservationComplete` / `transferExecutionComplete`, the CID is not on the payload — look the SRR up on the [subgraph](/subgraph/subgraph.md) by `srrId` to get the current `metadataDigest`.
  {% endhint %}

### `issueComplete`

Fires after one or more SRRs have been minted by the subscribed issuer.

**Event-specific fields**

<table><thead><tr><th width="260">Field</th><th width="140">Format</th><th>Description</th></tr></thead><tbody><tr><td><code>data[*].srrCid</code></td><td>string</td><td>CID of the SRR metadata on IPFS. Provided when the issuance flow knows the CID. See <a href="https://docs.ipfs.tech/concepts/content-addressing/#what-is-a-cid">CID documentation</a>.</td></tr></tbody></table>

{% tabs %}
{% tab title="Example payload" %}

```json
{
  "type": "issue_complete",
  "version": 2,
  "data": [
    {
      "groupId": "123456789012",
      "srrId": "123456789012",
      "collectionContractAddress": null,
      "srrCid": "bafkreid6i2u5b26hepprrkcswqoknzpyl2mrvuoy2ewuktj6ye5bxl3mby",
      "metadata": {
        "$schema": "https://api.startrail.io/api/v1/schema/registry-record-metadata.v2.1.schema.json",
        "$schemaIntegrity": "sha256-fff288406b907ee6472585388bf519573628e45592be368f128b5b1e37a947c9",
        "startbahnCertICTagUIDs": ["1234567890abcdef"],
        "title": {
          "en": "A title",
          "ja": "タイトル",
          "zh": "一个标题"
        },
        "size": {
          "width": 200,
          "height": 400,
          "depth": 12.4,
          "unit": "pixel",
          "flexibleDescription": {
            "en": "flexibleDescription comes here",
            "ja": "自由だーーー"
          }
        },
        "medium": {
          "en": "Oil on canvas",
          "ja": "キャンバスに油彩",
          "zh": "布面油画"
        },
        "edition": {
          "uniqueness": "unique work",
          "proofType": "ED",
          "number": 1,
          "totalNumber": 3,
          "note": { "en": "some extra notes in 1 or more languages" }
        },
        "contractTerms": {
          "royaltyRate": 15.7,
          "fileURL": "https://startrail.io/whitepaper/startrail_wp_en_v1.1.pdf"
        },
        "note": { "en": "note", "zh": "注意" },
        "thumbnailURL": "https://cdn.startrail.io/ipfs/bafkreigb2wjgin53xgmaiqxvdn4g2iw6cnmp4e3w3nzopmom53sjborque",
        "yearOfCreation": {
          "en": "around 2010-2020",
          "ja": "2010年から2020年頃"
        },
        "isDigital": true,
        "digitalDataHash": "sha256-247e4b904322a1dd0b148cd77e8627ec7d391251380880ab4621726ecb945ef5"
      }
    }
  ]
}
```

{% endtab %}
{% endtabs %}

### `transferReservationComplete`

Fires after a transfer reservation has been confirmed on-chain (the transfer-key flow). The subscribed previous owner receives the notification.

**Event-specific fields**

No fields beyond the [common SRR fields](#fields-common-to-all-srr-events).

{% hint style="info" %}
The legacy `transferCid`, `dataUrl`, and `encryptedTransferKey` fields are no longer emitted on this webhook. If you need the encrypted transfer key, fetch it through the separate transfer-key API.
{% endhint %}

{% tabs %}
{% tab title="Example payload" %}

```json
{
  "type": "transfer_key",
  "version": 2,
  "data": [
    {
      "groupId": "123456789012",
      "srrId": "123456789012",
      "collectionContractAddress": null,
      "metadata": {
        "$schema": "https://api.startrail.io/api/v1/schema/registry-record-metadata.v2.1.schema.json",
        "title": { "en": "A title" }
        // ... full SRR metadata JSON, same shape as on the IPFS CDN gateway
      }
    }
  ]
}
```

{% endtab %}
{% endtabs %}

### `transferExecutionComplete`

Fires after a transfer has executed on-chain and the SRR's owner has changed. The subscribed previous owner receives the notification.

**Event-specific fields**

<table><thead><tr><th width="260">Field</th><th width="140">Format</th><th>Description</th></tr></thead><tbody><tr><td><code>data[*].newOwnerEoa</code><mark style="color:red;">*</mark></td><td>string</td><td>EOA address of the SRR's new owner.</td></tr></tbody></table>

{% hint style="info" %}
The legacy `transferCid` field is no longer emitted on this webhook. If you need the transfer's provenance entry, query it from the [subgraph](/subgraph/subgraph.md) by `srrId`.
{% endhint %}

{% tabs %}
{% tab title="Example payload" %}

```json
{
  "type": "transfer_complete",
  "version": 2,
  "data": [
    {
      "groupId": "123456789012",
      "srrId": "123456789012",
      "collectionContractAddress": null,
      "newOwnerEoa": "0x887C0d2340d2Fa144289C2E2BF835556f5c6C4E0",
      "metadata": {
        "$schema": "https://api.startrail.io/api/v1/schema/registry-record-metadata.v2.1.schema.json",
        "title": { "en": "A title" }
        // ... full SRR metadata JSON, same shape as on the IPFS CDN gateway
      }
    }
  ]
}
```

{% endtab %}
{% endtabs %}

## Collection webhook events

Collection events relate to the lifecycle of a collection contract owned by your LUW. The two events share a common set of fields in addition to the envelope.

### Fields common to all collection events

<table><thead><tr><th width="260">Field</th><th width="140">Format</th><th>Description</th></tr></thead><tbody><tr><td><code>data[*].name</code><mark style="color:red;">*</mark></td><td>string</td><td>Name of the collection.</td></tr><tr><td><code>data[*].symbol</code><mark style="color:red;">*</mark></td><td>string</td><td>Symbol of the collection.</td></tr></tbody></table>

For collection events, `groupId` is composed as `${ownerAddress}${name}` and matches across the success and failure events for the same attempt.

### `collectionCreated`

Fires after a collection contract has been deployed.

**Event-specific fields**

<table><thead><tr><th width="260">Field</th><th width="140">Format</th><th>Description</th></tr></thead><tbody><tr><td><code>data[*].contractAddress</code><mark style="color:red;">*</mark></td><td>string</td><td>Address of the newly deployed collection contract.</td></tr><tr><td><code>data[*].ownerAddress</code><mark style="color:red;">*</mark></td><td>string</td><td>LUW address that owns the collection.</td></tr></tbody></table>

{% tabs %}
{% tab title="Example payload" %}

```json
{
  "type": "collection_created",
  "version": 1,
  "data": [
    {
      "groupId": "0x9f25c0d8eB5f461528ab5E02f1F31C77885d5Dc0collection name",
      "contractAddress": "0x229dbFE303C5706BDB570A42f0BA190621d2D032",
      "ownerAddress": "0x9f25c0d8eB5f461528ab5E02f1F31C77885d5Dc0",
      "name": "collection name",
      "symbol": "TT"
    }
  ]
}
```

{% endtab %}
{% endtabs %}

### `collectionCreateFailed`

Fires when a collection-creation transaction fails to be mined.

**Event-specific fields**

No fields beyond the [common collection fields](#fields-common-to-all-collection-events). The owner address can be recovered from the `groupId` (which encodes `${ownerAddress}${name}`) or correlated with the originating request on your side.

{% tabs %}
{% tab title="Example payload" %}

```json
{
  "type": "collection_create_failed",
  "version": 1,
  "data": [
    {
      "groupId": "0x9f25c0d8eB5f461528ab5E02f1F31C77885d5Dc0collection name",
      "name": "collection name",
      "symbol": "TT"
    }
  ]
}
```

{% endtab %}
{% endtabs %}

## Subscribing to webhooks

To enable webhook delivery, provide Startbahn with the following information.

<mark style="color:red;">`*`</mark> indicates the field is required.

<table><thead><tr><th width="220">Field</th><th width="320">Description</th><th>Format / Example</th></tr></thead><tbody><tr><td>Webhook URL<mark style="color:red;">*</mark></td><td>The HTTPS endpoint where Startbahn will POST events.</td><td><code>https://www.your-company.com/srr-integration-webhooks/</code></td></tr><tr><td>API Key<mark style="color:red;">*</mark></td><td>Shared secret that Startbahn will send in the <code>x-api-key</code> header. Use this on your side to authenticate the request. This key should be different from your Issue API key.</td><td>Any string matching <code>^[a-zA-Z0-9_+-]{30,100}$</code><br>Example: <code>e46b1263-e3f5-461d-bfe7-18aff21c5ed3</code></td></tr><tr><td>Public key</td><td>RSA public key used to encrypt the transfer key when using the transfer-key integration. You hold the private key and use it to decrypt. Only required when subscribing to <code>transferReservationComplete</code> with the transfer-key flow.</td><td>JSON object describing the key.<br>Example: <code>{"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA","n":"...","..."}</code><br><br>You can generate a key pair at <a href="https://codesandbox.io/s/subtlecrypto-rsa-ttql3">this SubtleCrypto example</a> or with any tool using equivalent parameters.</td></tr><tr><td>Number of tries</td><td>Total delivery attempts before the event is cancelled, integer <code>1 ≤ n ≤ 5</code>. Defaults to <code>1</code> if omitted.</td><td>Integer.<br><code>1</code> = single attempt, no retries.<br><code>2</code> = single attempt + 1 retry.</td></tr><tr><td>Contact email</td><td>Email address that receives a CSV with the event data when all delivery attempts have failed.</td><td><code>admin@webhook-client.com</code></td></tr><tr><td>Webhook events to subscribe to<mark style="color:red;">*</mark></td><td>The subscription names you want to receive. You can change the subscription set at any time for future events.</td><td>List of subscription names from the <a href="#events-at-a-glance">events table</a>.</td></tr></tbody></table>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.startrail.io/issue-transfer-api/issue-and-transfer-srr-nft/webhook-setup.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
