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

# Webhook endpoints

> Manage webhook endpoints that receive real-time event notifications. Includes HMAC-SHA256 signature verification, event types, and retry behavior.

The Webhooks API allows you to manage webhook endpoints that receive real-time event notifications when activities occur in your account.

## List webhook endpoints

```
GET /v1/webhook_endpoints
```

Returns a paginated list of webhook endpoints for the current account.

<ParamField query="page" type="integer" default="1">
  Page number for pagination.
</ParamField>

```bash theme={null}
curl https://api.gettrxn.com/v1/webhook_endpoints \
  -H "Authorization: Bearer $TRXN_TOKEN"
```

```json Response theme={null}
{
  "webhook_endpoints": [
    {
      "id": "we_xxx",
      "url": "https://example.com/webhook",
      "description": "Production webhook",
      "enabled": true,
      "subscriptions": ["*"],
      "created_at": "2025-01-14T12:00:00Z",
      "updated_at": "2025-01-14T12:00:00Z",
      "signing_secret_last4": "a1b2"
    }
  ],
  "pagination": {
    "page": 1,
    "pages": 1,
    "count": 1
  }
}
```

## Get webhook endpoint

```
GET /v1/webhook_endpoints/:id
```

Returns details of a specific webhook endpoint.

<ParamField path="id" type="string" required>
  The webhook endpoint's ID (e.g., `we_xxx`).
</ParamField>

```bash theme={null}
curl https://api.gettrxn.com/v1/webhook_endpoints/we_xxx \
  -H "Authorization: Bearer $TRXN_TOKEN"
```

```json Response theme={null}
{
  "id": "we_xxx",
  "url": "https://example.com/webhook",
  "description": "Production webhook",
  "enabled": true,
  "subscriptions": ["invoice.created", "invoice.paid"],
  "created_at": "2025-01-14T12:00:00Z",
  "updated_at": "2025-01-14T12:00:00Z",
  "signing_secret_last4": "a1b2"
}
```

## Create webhook endpoint

```
POST /v1/webhook_endpoints
```

Creates a new webhook endpoint.

<ParamField body="url" type="string" required>
  The URL where webhook events will be sent via HTTP POST. Must use HTTPS.
</ParamField>

<ParamField body="subscriptions" type="array" required>
  Event types to subscribe to. Use `["*"]` for all events, or specify individual events like `["invoice.created", "invoice.paid"]`.
</ParamField>

<ParamField body="enabled" type="boolean" default="true">
  Whether the endpoint is active.
</ParamField>

<ParamField body="description" type="string">
  Optional description for the endpoint.
</ParamField>

```bash theme={null}
curl -X POST https://api.gettrxn.com/v1/webhook_endpoints \
  -H "Authorization: Bearer $TRXN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhook",
    "subscriptions": ["invoice.created", "invoice.paid"],
    "enabled": true,
    "description": "Invoice notifications"
  }'
```

<Warning>
  The signing secret is only returned in full once -- at creation time. Store it securely as you will need it to verify webhook signatures.
</Warning>

The response includes the full `signing_secret` (returned as `201 Created`).

## Update webhook endpoint

```
PUT /v1/webhook_endpoints/:id
```

Updates an existing webhook endpoint.

<ParamField path="id" type="string" required>
  The webhook endpoint's ID.
</ParamField>

<ParamField body="url" type="string">
  The endpoint URL.
</ParamField>

<ParamField body="subscriptions" type="array">
  Event types to subscribe to.
</ParamField>

<ParamField body="enabled" type="boolean">
  Whether the endpoint is active.
</ParamField>

<ParamField body="description" type="string">
  Optional description.
</ParamField>

```bash theme={null}
curl -X PUT https://api.gettrxn.com/v1/webhook_endpoints/we_xxx \
  -H "Authorization: Bearer $TRXN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": false
  }'
```

Returns the updated webhook endpoint.

## Delete webhook endpoint

```
DELETE /v1/webhook_endpoints/:id
```

Deletes a webhook endpoint. This also deletes all associated webhook events.

<ParamField path="id" type="string" required>
  The webhook endpoint's ID.
</ParamField>

```bash theme={null}
curl -X DELETE https://api.gettrxn.com/v1/webhook_endpoints/we_xxx \
  -H "Authorization: Bearer $TRXN_TOKEN"
```

Returns `204 No Content`.

## Regenerate signing secret

```
POST /v1/webhook_endpoints/:id/regenerate_secret
```

Regenerates the signing secret for a webhook endpoint. The previous secret is immediately invalidated.

<ParamField path="id" type="string" required>
  The webhook endpoint's ID.
</ParamField>

```bash theme={null}
curl -X POST https://api.gettrxn.com/v1/webhook_endpoints/we_xxx/regenerate_secret \
  -H "Authorization: Bearer $TRXN_TOKEN"
```

Returns the webhook endpoint with the new full `signing_secret`.

<Warning>
  After regenerating the secret, you must update your integration to use the new secret. Webhooks signed with the old secret will fail verification.
</Warning>

## Webhook security

TRXN signs all webhook requests with HMAC-SHA256 signatures, allowing you to verify that webhooks are genuinely from TRXN.

### Signature header format

Every webhook request includes an `X-Trxn-Signature` header:

```
X-Trxn-Signature: t=<unix_timestamp>,v1=<signature>
```

Example:

```
X-Trxn-Signature: t=1704067200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
```

### Signing secret format

Signing secrets use the format `whsec_<64_hex_chars>`:

```
whsec_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
```

### Verifying signatures

To verify a webhook signature:

1. Extract the timestamp (`t`) and signature (`v1`) from the header.
2. Check the timestamp is within 5 minutes of current time (prevents replay attacks).
3. Compute the expected signature: `HMAC-SHA256(secret, "<timestamp>.<raw_body>")`.
4. Compare signatures using constant-time comparison.

<Note>
  Use the full signing secret (including the `whsec_` prefix) as the HMAC key. This matches Stripe's implementation.
</Note>

<CodeGroup>
  ```ruby Ruby theme={null}
  def verify_webhook(payload, signature_header, secret)
    parts = signature_header.split(",").to_h { |p| p.split("=", 2) }
    timestamp = parts["t"].to_i
    signature = parts["v1"]

    # Check timestamp is within 5 minutes
    return false if (Time.now.to_i - timestamp).abs > 300

    # Compute expected signature (use full secret including whsec_ prefix)
    signed_payload = "#{timestamp}.#{payload}"
    expected = OpenSSL::HMAC.hexdigest("SHA256", secret, signed_payload)

    # Constant-time comparison
    ActiveSupport::SecurityUtils.secure_compare(expected, signature)
  end
  ```

  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function verifyWebhook(payload, signatureHeader, secret) {
    const parts = Object.fromEntries(
      signatureHeader.split(',').map(p => p.split('='))
    );
    const timestamp = parseInt(parts.t, 10);
    const signature = parts.v1;

    // Check timestamp is within 5 minutes
    if (Math.abs(Date.now() / 1000 - timestamp) > 300) {
      return false;
    }

    // Compute expected signature (use full secret including whsec_ prefix)
    const signedPayload = `${timestamp}.${payload}`;
    const expected = crypto
      .createHmac('sha256', secret)
      .update(signedPayload)
      .digest('hex');

    return crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signature)
    );
  }
  ```

  ```python Python theme={null}
  import hmac
  import hashlib
  import time

  def verify_webhook(payload: str, signature_header: str, secret: str) -> bool:
      parts = dict(p.split("=", 1) for p in signature_header.split(","))
      timestamp = int(parts["t"])
      signature = parts["v1"]

      # Check timestamp is within 5 minutes
      if abs(time.time() - timestamp) > 300:
          return False

      # Compute expected signature (use full secret including whsec_ prefix)
      signed_payload = f"{timestamp}.{payload}"
      expected = hmac.new(
          secret.encode(),
          signed_payload.encode(),
          hashlib.sha256
      ).hexdigest()

      return hmac.compare_digest(expected, signature)
  ```
</CodeGroup>

## Available events

<AccordionGroup>
  <Accordion title="Customer events">
    | Event              | Description                      |
    | ------------------ | -------------------------------- |
    | `customer.created` | A new customer has been created. |
    | `customer.updated` | A customer has been updated.     |
    | `customer.deleted` | A customer has been deleted.     |
  </Accordion>

  <Accordion title="Product events">
    | Event             | Description                     |
    | ----------------- | ------------------------------- |
    | `product.created` | A new product has been created. |
    | `product.updated` | A product has been updated.     |
    | `product.deleted` | A product has been deleted.     |
  </Accordion>

  <Accordion title="Price events">
    | Event           | Description                   |
    | --------------- | ----------------------------- |
    | `price.created` | A new price has been created. |
    | `price.updated` | A price has been updated.     |
    | `price.deleted` | A price has been deleted.     |
  </Accordion>

  <Accordion title="Invoice events">
    | Event             | Description                         |
    | ----------------- | ----------------------------------- |
    | `invoice.created` | A new invoice has been created.     |
    | `invoice.updated` | An invoice has been updated.        |
    | `invoice.paid`    | An invoice has been marked as paid. |
    | `invoice.overdue` | An invoice has become overdue.      |
  </Accordion>

  <Accordion title="Subscription events">
    | Event                    | Description                          |
    | ------------------------ | ------------------------------------ |
    | `subscription.created`   | A new subscription has been created. |
    | `subscription.activated` | A subscription has become active.    |
    | `subscription.canceled`  | A subscription has been canceled.    |
    | `subscription.renewed`   | A subscription has been renewed.     |
  </Accordion>

  <Accordion title="Subscription phase events">
    | Event                        | Description                                |
    | ---------------------------- | ------------------------------------------ |
    | `subscription_phase.created` | A new subscription phase has been created. |
    | `subscription_phase.updated` | A subscription phase has been updated.     |
    | `subscription_phase.deleted` | A subscription phase has been deleted.     |
  </Accordion>

  <Accordion title="Wallet events">
    | Event            | Description                    |
    | ---------------- | ------------------------------ |
    | `wallet.created` | A new wallet has been created. |
  </Accordion>

  <Accordion title="Crypto address events">
    | Event                    | Description                                      |
    | ------------------------ | ------------------------------------------------ |
    | `crypto_address.created` | A new crypto address has been added to a wallet. |
    | `crypto_address.deleted` | A crypto address has been deleted.               |
  </Accordion>

  <Accordion title="Payment claim events">
    | Event                     | Description                               |
    | ------------------------- | ----------------------------------------- |
    | `payment_claim.submitted` | A customer has submitted a payment claim. |
    | `payment_claim.approved`  | A payment claim has been approved.        |
    | `payment_claim.rejected`  | A payment claim has been rejected.        |
  </Accordion>

  <Accordion title="Crypto transaction events">
    | Event                          | Description                                     |
    | ------------------------------ | ----------------------------------------------- |
    | `crypto_transaction.received`  | A crypto transaction has been received.         |
    | `crypto_transaction.allocated` | A transaction has been allocated to an invoice. |
  </Accordion>

  <Accordion title="Transaction allocation events">
    | Event                            | Description                                    |
    | -------------------------------- | ---------------------------------------------- |
    | `transaction_allocation.created` | A new transaction allocation has been created. |
  </Accordion>
</AccordionGroup>

## Webhook payload format

When an event occurs, TRXN sends a POST request to your endpoint with the following format:

```json theme={null}
{
  "event": "invoice.created",
  "payload": {
    "id": "inv_xxx",
    "object": "invoice",
    "customer_id": "cus_xxx",
    "status": "pending",
    "total_amount": "100.00"
  },
  "timestamp": "2025-01-14T12:00:00Z"
}
```

### Subscription phase payload example

```json theme={null}
{
  "event": "subscription_phase.created",
  "payload": {
    "id": "sub_phase_xxx",
    "object": "subscription_phase",
    "subscription_id": "sub_xxx",
    "start_date": "2025-02-01T00:00:00Z",
    "end_date": "2025-03-01T00:00:00Z",
    "items": [
      {
        "id": 123,
        "price_id": "price_xxx",
        "quantity": 1,
        "overridden_price_amount": null
      }
    ],
    "created_at": "2025-01-26T12:00:00Z",
    "updated_at": "2025-01-26T12:00:00Z"
  },
  "timestamp": "2025-01-26T12:00:00Z"
}
```

## Response handling

Your endpoint should respond with a 2xx status code to indicate successful receipt. If your endpoint returns a non-2xx status code, TRXN will retry the delivery with exponential backoff (up to 11 attempts).

### Error handling

If your endpoint cannot be reached, TRXN will:

1. **Connection error**: Disable the endpoint to prevent further delivery attempts.
2. **Timeout error**: Retry with exponential backoff.
3. **TLS error**: Retry with exponential backoff.
4. **Non-2xx response**: Retry with exponential backoff.

## Event retention

TRXN retains webhook events for **30 days**.

## Best practices

<Tip>
  Return a 2xx response before any complex logic to avoid timeouts. Process the event asynchronously after responding.
</Tip>

1. **Respond quickly**: Return a 2xx response before any complex logic that could cause a timeout. Process the event asynchronously after responding.
2. **Handle duplicates**: Webhook endpoints may occasionally receive the same event more than once. Make your event processing idempotent by tracking the `id` from the payload. If you have already processed an event for that resource, skip it and return a 2xx response.
3. **Handle out-of-order delivery**: Events may not arrive in chronological order. Use the `timestamp` field to determine the sequence of events rather than assuming arrival order.
4. **Always verify signatures**: Use the `X-Trxn-Signature` header to verify all incoming webhooks. Reject any webhook that fails signature verification.
5. **Use HTTPS**: Always use HTTPS endpoints for security.
6. **Store secrets securely**: Store your signing secrets in environment variables or a secrets manager, never in source code.
7. **Monitor failures**: Check webhook events in the dashboard to monitor delivery status.
8. **Regenerate compromised secrets**: If you suspect your signing secret has been compromised, regenerate it immediately.
