> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://help.goandance.com/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# Webhooks

Webhooks allow you to **receive real-time notifications in your own system when certain actions occur on go&dance**, such as when a purchase is created or updated.

Instead of periodically checking go&dance for changes, you can configure a destination URL where go&dance will automatically send an HTTP request whenever one of the events supported by the integration occurs.

Once webhooks are **enabled for your account, they apply to all events for which you are the owner**. You do not need to configure webhooks separately for each event.

Each notification includes information about the event that triggered it and the corresponding data, allowing you to integrate go&dance with your own applications, databases, CRM, ERP, or other external systems.

> **Important:** webhooks are generated only for events for which your user is the **owner**. Events that you can access or manage but do not own are not included.

# Table of contents

- [Request headers](#1-request-headers)
- [Validate the signature](#1-validate-the-signature)
- [Prevent duplicate processing](#1-prevent-duplicate-processing)
- [Endpoint response](#1-endpoint-response)
- [Payload](#1-payload)
- [Data structures](#1-data-structures)
    - [PurchaseDto](#2-purchasedto)
    - [EventDto](#2-eventdto)
    - [BuyerDto](#2-buyerdto)
    - [ProductDto](#2-productdto)
    - [PurchaseDeletedDto](#2-purchasedeleteddto)
    - [TicketDto](#2-ticketdto)
    - [AttendeeDto](#2-attendeedto)
- [Associate go&dance events and tickets with your own IDs](#1-associate-godance-events-and-tickets-with-your-own-ids)
- [Security and best practices](#1-security-and-best-practices)

---

When one of the events subscribed to by an active webhook occurs, go&dance sends an HTTP request to the configured destination URL.

The request contains:

- Headers with information about the event and delivery.
- A JSON-formatted `body` containing the event data.
- An HMAC-SHA256 signature that allows you to verify that the request was generated by go&dance.

# Request headers

The specific headers sent by go&dance are:

| Header                  | Description                                                                                                                                                             |
|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `X-GoDance-Log-Id`      | Unique identifier of the action that triggered the webhook. It remains the same across retries and should be used to prevent processing the same action more than once. |
| `X-GoDance-Delivery-Id` | Unique identifier of a delivery attempt. Each retry generates a new identifier.                                                                                         |
| `X-GoDance-Signature`   | HMAC-SHA256 signature used to verify the authenticity of the request.                                                                                                   |
| `X-GoDance-Timestamp`   | Timestamp used as part of the signature calculation.                                                                                                                    |
| `X-GoDance-Type`        | Type of event that triggered the notification.                                                                                                                          |

Example:

```http
User-Agent: GoDance/Webhooks
Content-Type: application/json
X-GoDance-Log-Id: 2d0df0a0-63c5-48bc-8484-535a5fb0aad6
X-GoDance-Delivery-Id: 6d498a41-d1ad-4ccc-8abf-1045cc52cbac
X-GoDance-Signature: 8ae2fe919f8412ed9001397356a38fa19c4939235511c7a550041454b628473c
X-GoDance-Timestamp: 1790245818
X-GoDance-Type: PURCHASE.UPDATED
```

### `X-GoDance-Log-Id`

Uniquely identifies the action that triggered the webhook.

The same action may have several delivery attempts. For example, if a delivery fails and is later retried, all those attempts correspond to the same
`X-GoDance-Log-Id`.

Your integration should use this value to prevent processing the same action more than once.

### `X-GoDance-Delivery-Id`

Identifies a specific delivery attempt.

If a webhook delivery is retried, a new `X-GoDance-Delivery-Id` will be generated, while the `X-GoDance-Log-Id` will remain the same.

Therefore:

```text
Action
X-GoDance-Log-Id: A
    │
    ├── Delivery 1 → X-GoDance-Delivery-Id: B
    │
    ├── Delivery 2 → X-GoDance-Delivery-Id: C
    │
    └── Delivery 3 → X-GoDance-Delivery-Id: D
```

Use `X-GoDance-Log-Id`, not `X-GoDance-Delivery-Id`, as the idempotency key.

### `X-GoDance-Type`

Indicates the event that triggered the notification.

For example:

```text
PURCHASE.CREATED
```

The same type is included in the `type` property of the request body.

### `X-GoDance-Timestamp`

Timestamp generated for the delivery.

This value is part of the data used to calculate the signature.

### `X-GoDance-Signature`

HMAC-SHA256 signature generated using the webhook secret.

You must validate this signature before processing the received information.

---

# Validate the signature

Each webhook has a secret with a format similar to:

```text
gd_sec_********************************
```

go&dance uses this secret to generate an HMAC-SHA256 signature for each delivery.

The signature is calculated using the following concatenation:

```text
{deliveryId}|{timestamp}|{body}
```

where:

- `deliveryId` is the value of `X-GoDance-Delivery-Id`.
- `timestamp` is the value of `X-GoDance-Timestamp`.
- `body` is **exactly the original request body as received in the HTTP request**.

The calculation is equivalent to:

```text
HMAC-SHA256(
    deliveryId + "|" + timestamp + "|" + body,
    secret
)
```

The result must match:

```text
X-GoDance-Signature
```

## Important: always use the original body

To validate the signature correctly, you must use the **original request body exactly as it was received**.

Do not parse, transform, modify, or reserialize the JSON before calculating the signature.

For example, this flow is incorrect:

```text
Received body
    ↓
JSON.parse(...)
    ↓
JSON.stringify(...)
    ↓
Calculate signature  ❌
```

Even if the resulting JSON represents exactly the same data, its representation may have changed.

Seemingly irrelevant changes such as spaces, line breaks, character escaping, or property order can produce different content and therefore a different HMAC
signature.

The correct flow is:

```text
Original body received
       │
       ├──────────────► Calculate and validate signature
       │
       ▼
   JSON.parse(...)
       │
       ▼
 Process event
```

**First validate the signature using the original body. Then you can parse and process the JSON.**

---

## TypeScript example

The following example uses a Node.js HTTP server and preserves the original body before converting it to JSON:

```typescript
import crypto from 'node:crypto';

const secret = 'gd_sec_...';

// Get the headers
const deliveryId = request.headers['x-godance-delivery-id'];
const timestamp = request.headers['x-godance-timestamp'];
const signature = request.headers['x-godance-signature'];
const logId = request.headers['x-godance-log-id'];

// IMPORTANT: get the original body without parsing or serializing it
const rawBody = request.rawBody;

// Calculate the expected signature
const expectedSignature = crypto
  .createHmac('sha256', secret)
  .update(`${deliveryId}|${timestamp}|${rawBody}`)
  .digest('hex');

// Validate the signature
if (signature !== expectedSignature) {
  return response.status(401).send('Invalid signature');
}

// Prevent processing the same action twice
if (await alreadyProcessed(logId)) {
  return response.status(200).send('OK');
}

// Once the signature has been validated, we can parse the body
const payload = JSON.parse(rawBody);

await processWebhook(payload);
await markAsProcessed(logId);

return response.status(200).send('OK');
```

> If you use Express, NestJS, or another framework that automatically transforms the request body, make sure you preserve the **raw body** before the framework
> processes it. The signature must always be calculated using the original bytes received.

---

# Prevent duplicate processing

Webhooks should be processed **idempotently**.

The same action may generate several delivery attempts if, for example, a previous attempt failed.

Each attempt will have a different `X-GoDance-Delivery-Id`, but they will all share the same:

```text
X-GoDance-Log-Id
```

Therefore, you should use `X-GoDance-Log-Id` to determine whether an action has already been processed.

A common strategy is:

1. Receive the webhook and validate its signature.
2. Check whether the `X-GoDance-Log-Id` already exists in your system.
3. If it already exists, do not execute the operation again.
4. If it does not exist, process the event and store the `X-GoDance-Log-Id`.

> Do not use `X-GoDance-Delivery-Id` as the idempotency key. A retry of the same action will have a different delivery identifier.

---

# Endpoint response

go&dance considers any HTTP response with a **`2xx`** status code to be successful.

For example:

```text
200 OK
201 Created
202 Accepted
204 No Content
```

Any `2xx` response is considered a successful delivery.

Responses outside the `2xx` range are considered failed deliveries and will appear as such in **Webhook logs**.

---

# Payload

The request body is sent in JSON format:

```http
Content-Type: application/json
```

Its general structure is:

```json
{
  "type": "PURCHASE.UPDATED",
  "data": {
    ...
  }
}
```

The following events are currently available:

| Event              | Payload             | Description                                                      |
|--------------------|---------------------|------------------------------------------------------------------|
| `PURCHASE.CREATED` | `PurchaseDto`       | Triggered when a purchase is created, whether online or offline. |
| `PURCHASE.UPDATED` | `PurchaseDto`       | Triggered when a purchase is updated.                            |
| `PURCHASE.DELETED` | `DeletedProductDto` | Triggered when a product is deleted from a purchase.             |

`Payload` is the data structure received in the `data` field.

---

# Data structures

## PurchaseDto

| Field           | Type           | Required | Description                                                                                                                        |
|-----------------|----------------|----------|------------------------------------------------------------------------------------------------------------------------------------|
| `uuid`          | `string`       | Yes      | Unique identifier of the purchase.                                                                                                 |
| `recordLocator` | `string`       | Yes      | Purchase record locator. This is the identifier the user sees in their purchase.                                                   |
| `event`         | `EventDto`     | Yes      | Event associated with the purchase.                                                                                                |
| `buyer`         | `BuyerDto`     | No       | Buyer information.                                                                                                                 |
| `type`          | `string`       | Yes      | Origin of the purchase. It can contain `manual` for manually generated orders or `online` for purchases made through the platform. |
| `amount`        | `number`       | Yes      | Purchase amount. Includes all additional charges such as booking fees, cancellation insurance, etc.                                |
| `promoterCode`  | `string`       | No       | Associated promoter code, when applicable.                                                                                         |
| `discountCode`  | `string`       | No       | Associated discount code, when applicable.                                                                                         |
| `products`      | `ProductDto[]` | Yes      | Products included in the purchase.                                                                                                 |
| `createdAt`     | `string`       | Yes      | Purchase creation date.                                                                                                            |

## EventDto

| Field      | Type     | Required | Description                              |
|------------|----------|----------|------------------------------------------|
| `uuid`     | `string` | Yes      | Unique identifier of the event.          |
| `customId` | `string` | No       | Custom event identifier, when available. |
| `name`     | `string` | Yes      | Event name.                              |

## BuyerDto

| Field   | Type     | Required |
|---------|----------|----------|
| `name`  | `string` | Yes      |
| `email` | `string` | Yes      |

The `buyer` property may be absent or have a `null` value.

## ProductDto

Each product has the following structure:

| Field                    | Type            | Required | Description                                                                                                                              |
|--------------------------|-----------------|----------|------------------------------------------------------------------------------------------------------------------------------------------|
| `uuid`                   | `string`        | Yes      | Unique identifier of the product.                                                                                                        |
| `ticketCode`             | `string`        | No       | Product record locator. This is the identifier the user sees in their purchase and the code used to scan the ticket with the mobile app. |
| `type`                   | `string`        | Yes      | Product type. Possible values for this field include `ticket`, `distribution-fee`, `gateway-fee`, `cancellation-insurance`, or others.   |
| `status`                 | `CANCELED`      | Yes      | Product status.                                                                                                                          |
| `onBehalfOfOrganization` | `boolean`       | Yes      | Indicates whether the product belongs to the organizer or is a go&dance product, such as distribution fees.                              |
| `ticket`                 | `TicketDto`     | No       | Ticket information, when applicable.                                                                                                     |
| `amount`                 | `number`        | Yes      | Product price. This is the sale price that you, as the organizer, have set for the product.                                              |
| `refundedAmount`         | `number`        | Yes      | If refunds have been issued, the amount refunded to the user for this product.                                                           |
| `platformFee`            | `number`        | Yes      | Platform commission. This is the commission charged to the organizer for the sale of this ticket when the settlement is processed.       |
| `attendees`              | `AttendeeDto[]` | Yes      | Attendees associated with the product.                                                                                                   |

> Products with `onBehalfOfOrganization = true` are products sold **on behalf of the organizer**, such as event tickets. In these cases, the organizer is
> responsible for the sale and, when applicable, for **issuing the corresponding invoice to the buyer**.
>
> Products with `onBehalfOfOrganization = false` are products or services sold directly by **go&dance**. In these cases, go&dance is responsible for their
> management and the corresponding invoicing. These are typically items such as **booking fees, Flexible Cancellation, or other services offered directly by
go&dance**.

## PurchaseDeletedDto

| Field           | Type     | Required | Description                                                                                                                              |
|-----------------|----------|----------|------------------------------------------------------------------------------------------------------------------------------------------|
| `uuid`          | `string` | Yes      | Unique identifier of the purchase.                                                                                                       |
| `recordLocator` | `string` | Yes      | Purchase record locator. This is the identifier the user sees in their purchase.                                                         |
| `productUuid`   | `string` | Yes      | Unique identifier of the product.                                                                                                        |
| `ticketCode`    | `string` | Yes      | Product record locator. This is the identifier the user sees in their purchase and the code used to scan the ticket with the mobile app. |

The `PURCHASE.DELETED` event is sent by `POST` whenever a ticket is deleted from a purchase. The `data` field contains a `DeletedProductDto` with the purchase and deleted product identifiers.

If several tickets are deleted from the same purchase, go&dance sends **one separate webhook request for each deleted ticket**. For example, deleting three tickets from the same purchase generates three `PURCHASE.DELETED` requests, each containing the `productUuid` and `ticketCode` of the corresponding deleted ticket.

> **Important:** do not assume that a single `PURCHASE.DELETED` request represents all tickets deleted from a purchase. Process each request independently.

### Product types

Product types currently in use include:

```text
ticket
distribution-fee
gateway-fee
cancellation-insurance
```

The integration must be prepared to receive other `type` values in the future.

> Do not assume that all products are tickets. Depending on the `type`, properties such as `ticket` may be absent or `null`.

## TicketDto

When the product corresponds to a ticket, `ticket` contains:

| Field      | Type            | Required |
|------------|-----------------|----------|
| `uuid`     | `string`        | Yes      |
| `customId` | `string` `null` | No       |
| `name`     | `string`        | Yes      |

## AttendeeDto

Each element in `attendees` has the following structure:

| Field       | Type      | Required | Description                                             |
|-------------|-----------|----------|---------------------------------------------------------|
| `uuid`      | `string`  | Yes      | Unique identifier of the attendee.                      |
| `position`  | `number`  | Yes      | Attendee's position within the product.                 |
| `completed` | `boolean` | Yes      | Indicates whether the attendee information is complete. |
| `values`    | `object`  | Yes      | Values associated with the attendee information fields. |

The contents of `values` are dynamic and depend on the information requested for the ticket.

For example:

```json
{
  "fullName": "Example Attendee",
  "email": "attendee@example.com",
  "gender": "male",
  "role": "leader",
  "residenceCountry": "IT",
  "phone": "+00 000 000 000"
}
```

The integration should not assume that these properties will always be present or that they are the only properties available.

---

# Associate go&dance events and tickets with your own IDs

If you integrate go&dance with your own system, you can use **custom IDs (`customId`)** to associate go&dance events and tickets with the corresponding records
in your database.

You can assign your own **alphanumeric ID** to each event and each ticket type. This allows you to identify them using the same references you already use in
your system.

Alternatively, you can store the **go&dance `uuid` values** in your database and use those identifiers for the association. You can choose whichever approach
best fits your integration.

## Configure the custom ID for an event

To assign your own ID to an event, go to:

**Event page > Configuration > Additional event information**

Enter your identifier in the **Custom ID** field.

This value is returned in the webhook payload as:

```text
event.customId
```

For example:

```json
{
  "event": {
    "uuid": "goanddance-event-uuid",
    "customId": "MY-EVENT-2026",
    "name": "My Event"
  }
}
```

You can then use either `uuid` or `customId` to associate the event with the corresponding event in your database.

## Configure the custom ID for a ticket

You can also assign your own ID to each ticket type.

Edit the ticket and open the **Advanced** tab. Enter your identifier in the **Custom ID** field.

This value is returned in the webhook payload inside `ticket`:

```text
products[].ticket.customId
```

For example:

```json
{
  "ticket": {
    "uuid": "goanddance-ticket-uuid",
    "customId": "FULL-PASS-2026",
    "name": "Full Pass"
  }
}
```

This allows you to associate each go&dance ticket type with the corresponding product, ticket, or record in your own database.

## Which identifier should I use?

Both approaches are valid:

- **Use `customId`** if you want go&dance to return the identifiers you already use in your own system.
- **Use `uuid`** if you prefer to store go&dance identifiers in your database and use them to create the association.

For example:

```text
Your database                  go&dance
────────────────────────────────────────────
Event EVT-2026       ←────→    event.customId
Full Pass FP-01      ←────→    ticket.customId
```

or:

```text
Your database                  go&dance
────────────────────────────────────────────
Event                  ←────→   event.uuid
Ticket type            ←────→   ticket.uuid
```

> **Important:** `customId` is optional. Your integration should therefore not assume that it will always have a value. If you rely on `customId` to associate
> records between systems, make sure you have configured it for the corresponding events and tickets.

---

# Security and best practices

For a secure and reliable integration:

- Use HTTPS for the destination URL.
- Store the webhook secret securely.
- Do not expose the secret in client-side code or public repositories.
- Always validate `X-GoDance-Signature` before processing the payload.
- Calculate the signature exclusively using the **original body received**, without parsing or reserializing it.
- Use a timing-attack-resistant comparison when verifying the signature.
- Use `X-GoDance-Log-Id` as the idempotency key.
- Do not use `X-GoDance-Delivery-Id` to detect duplicate actions.
- Do not assume that product types are limited to the values currently known.
- Regenerate the secret if you believe it may have been compromised.