> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gomry.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive real-time POST notifications when tickets are purchased and forms are submitted

# Webhooks

Webhooks let Gomry notify your own systems the moment something happens in your
organization — a ticket is purchased, a subscription changes, or a form is
submitted. Instead of polling the API, you register one or more HTTPS URLs and
Gomry sends an HTTP `POST` request with a JSON payload to each of them as events
occur.

## Configuring webhook URLs

Webhook endpoints are configured **per organization**.

<Steps>
  <Step title="Open your integration settings">
    Go to **Organization Settings → Integrations** in your Gomry dashboard.
  </Step>

  <Step title="Add your endpoint URLs">
    Enter one or more endpoint URLs, **one per line**. Every URL you add receives
    a copy of every webhook event for your organization.
  </Step>

  <Step title="Save">
    Save your settings. New events will start being delivered to your endpoints.
  </Step>
</Steps>

<Note>
  Use an `https://` endpoint that is publicly reachable and responds quickly (see
  [Delivery & reliability](#delivery--reliability)). Webhooks are scoped to the
  whole organization — there is currently no per-event subscription.
</Note>

## How delivery works

Each event is sent as a separate HTTP request to every configured URL:

| Property     | Value                                                          |
| ------------ | -------------------------------------------------------------- |
| Method       | `POST`                                                         |
| Content-Type | `application/json`                                             |
| Body         | A single JSON event object (see [Payload](#payload-structure)) |
| Timeout      | 5 seconds                                                      |
| Retries      | None — delivery is best-effort                                 |

Your endpoint should respond with a `2xx` status code as quickly as possible.
Do any heavy processing **after** acknowledging the request (for example, by
pushing the payload onto a queue), so you don't hit the delivery timeout.

<Warning>
  Webhook payloads are **not currently signed**, and there is no shared secret or
  signature header on this delivery path. Treat the data as advisory: keep your
  endpoint URL private, accept requests only over HTTPS, and confirm anything
  sensitive (such as payment amounts) against the
  [Payments API](/api-reference/payments/get-payment) using the `payment.id`
  from the payload before acting on it.
</Warning>

## Event types

The `type` field identifies what happened. The most common event is
`payment.succeeded`, which fires when an attendee **buys a ticket**.

| `type`                  | When it fires                                                                                                       |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `payment.succeeded`     | A one-time payment completes — most commonly a **ticket purchase**. `payment.billing_reason` is `one_time_payment`. |
| `subscription.created`  | A recurring subscription is created. `payment.billing_reason` is `subscription_create`.                             |
| `subscription.deleted`  | A subscription is cancelled or ends.                                                                                |
| `form_response.created` | A form / application is submitted for the first time.                                                               |
| `form_response.updated` | An existing form / application response is updated (e.g. status change).                                            |

<Note>
  Some legacy form-response payloads omit the `type` field but always include
  `formID` and `applicationID`. Use the presence of those fields to identify a
  form-response event.
</Note>

## Payload structure

All events share one envelope. Fields are populated based on the event `type`,
so most are optional — always guard for missing values.

| Field                | Type              | Description                                                                |
| -------------------- | ----------------- | -------------------------------------------------------------------------- |
| `type`               | string            | The event type (see table above).                                          |
| `spaceID`            | string            | The space the event belongs to, when applicable.                           |
| `organizationID`     | string            | The organization the event belongs to.                                     |
| `date`               | string (ISO 8601) | When the event occurred.                                                   |
| `user`               | object            | The person who triggered the event (see below).                            |
| `payment`            | object            | Payment details — present on `payment.*` and `subscription.*` events.      |
| `subscription`       | object            | Subscription details — present on `subscription.*` events.                 |
| `lineItems`          | array             | Line items for the payment. Ticket line items have `type: "ticket"`.       |
| `formID`             | string            | The form ID — present on `form_response.*` events.                         |
| `applicationID`      | string            | The application/response ID — present on `form_response.*` events.         |
| `submission_answers` | object            | Submitted answers keyed by question — present on `form_response.*` events. |
| `responseStatus`     | string            | `Pending`, `Accepted`, `Rejected`, `Draft`, or `Deleted` — form responses. |
| `isNewApplication`   | boolean           | Whether this is the first submission — form responses.                     |

### `user` object

| Field                                                   | Type             | Description                                     |
| ------------------------------------------------------- | ---------------- | ----------------------------------------------- |
| `id`                                                    | string           | The user's ID.                                  |
| `contactID`                                             | string           | The contact record ID in your organization.     |
| `name`                                                  | string           | Full name.                                      |
| `firstName` / `lastName`                                | string           | Name parts (populated on form-response events). |
| `email`                                                 | string           | Email address.                                  |
| `phoneNumber`                                           | string           | Phone number.                                   |
| `linkedin`, `instagram`, `github`, `twitter`, `website` | string           | Social / web profiles, when provided.           |
| `bio`                                                   | string           | Short biography, when provided.                 |
| `location`                                              | string \| object | Location, when provided.                        |
| `country`                                               | string           | Country, when provided.                         |

### `payment` object

| Field            | Type   | Description                                                         |
| ---------------- | ------ | ------------------------------------------------------------------- |
| `id`             | string | The payment ID. Use this to look up the payment via the API.        |
| `grossAmount`    | number | Total amount charged, in the smallest currency unit.                |
| `netAmount`      | number | Amount after fees, in the smallest currency unit.                   |
| `currency`       | string | ISO 4217 currency code (e.g. `usd`).                                |
| `productID`      | string | The product ID.                                                     |
| `priceID`        | string | The price ID.                                                       |
| `billing_reason` | string | `one_time_payment`, `subscription_create`, or `subscription_cycle`. |

## Example: ticket purchase

When an attendee buys a ticket, your endpoint receives a `payment.succeeded`
event:

```json theme={null}
{
  "type": "payment.succeeded",
  "spaceID": "AbCdEfGhIjKlMnOpQrSt",
  "user": {
    "id": "user_123",
    "contactID": "contact_456",
    "name": "Jordan Rivera",
    "email": "jordan@example.com",
    "phoneNumber": "+15551234567",
    "linkedin": "https://linkedin.com/in/jordanrivera",
    "country": "US"
  },
  "date": "2026-06-11T18:30:00.000Z",
  "payment": {
    "id": "pay_789",
    "grossAmount": 5000,
    "netAmount": 4650,
    "currency": "usd",
    "productID": "prod_abc",
    "priceID": "price_def",
    "billing_reason": "one_time_payment"
  },
  "lineItems": [
    {
      "type": "ticket",
      "ticket_owner_email": "jordan@example.com",
      "quantity": 1
    }
  ]
}
```

## Example: form submission

```json theme={null}
{
  "type": "form_response.created",
  "formID": "form_123",
  "applicationID": "app_456",
  "user": {
    "id": "user_123",
    "contactID": "contact_456",
    "name": "Jordan Rivera",
    "firstName": "Jordan",
    "lastName": "Rivera",
    "email": "jordan@example.com"
  },
  "submission_answers": {
    "What's your company?": "Acme Inc.",
    "Dietary restrictions": "Vegetarian"
  },
  "responseStatus": "Pending",
  "isNewApplication": true,
  "date": "2026-06-11T18:30:00.000Z"
}
```

## Handling webhooks

<CodeGroup>
  ```javascript Node.js (Express) theme={null}
  import express from "express";

  const app = express();
  app.use(express.json());

  app.post("/webhooks/gomry", (req, res) => {
    const event = req.body;

    // Acknowledge immediately, then process asynchronously.
    res.status(200).send("ok");

    switch (event.type) {
      case "payment.succeeded":
        // A ticket was purchased — event.payment.id, event.user.email, …
        handleTicketPurchase(event);
        break;
      case "form_response.created":
      case "form_response.updated":
        handleFormResponse(event);
        break;
      default:
        // form-response events may arrive without a `type`
        if (event.formID) handleFormResponse(event);
    }
  });

  app.listen(3000);
  ```

  ```python Python (Flask) theme={null}
  from flask import Flask, request

  app = Flask(__name__)

  @app.post("/webhooks/gomry")
  def gomry_webhook():
      event = request.get_json()
      event_type = event.get("type")

      if event_type == "payment.succeeded":
          # A ticket was purchased
          handle_ticket_purchase(event)
      elif event_type in ("form_response.created", "form_response.updated") or event.get("formID"):
          handle_form_response(event)

      # Acknowledge quickly; process heavy work out of band.
      return "ok", 200
  ```
</CodeGroup>

## Best practices

<AccordionGroup>
  <Accordion title="Respond fast, process later">
    Return a `2xx` within the 5-second timeout. Offload anything slow (emails,
    database writes, third-party calls) to a background job or queue.
  </Accordion>

  <Accordion title="Make your handler idempotent">
    Because there are no delivery guarantees and multiple endpoints can be
    configured, design your handler so processing the same event twice is safe —
    key off `payment.id` or `applicationID`.
  </Accordion>

  <Accordion title="Verify sensitive data against the API">
    Payloads are unsigned. Before acting on money-related data, re-fetch it from
    the [Payments API](/api-reference/payments/get-payment) using the
    `payment.id` from the payload.
  </Accordion>

  <Accordion title="Guard for missing fields">
    Most fields are optional and vary by event type. Always check for existence
    before reading nested values.
  </Accordion>
</AccordionGroup>
