# Webhooks

> Send signed HTTP events to your own servers when records are created, updated, or deleted.

Webhooks let Workstation tell your systems when something changes. You register an endpoint URL and pick the events you care about. When a matching change happens, Workstation sends a signed `POST` request with the event to that URL and retries it if your server does not answer.

For example, you can notify your ERP when a deal is won, or update a data warehouse whenever a contact changes.

Open **Settings → Integrations & Developers → Webhooks**. You need the **Settings → Manage** permission to add or change endpoints; **Settings → View** can see them.

## Add an endpoint

1. Click **Add endpoint**.
2. Enter the **Endpoint URL**, such as `https://example.com/webhooks/repzo`.
3. Optionally add a **Description** of what the endpoint is for.
4. Under **Events to send**, pick events or turn on **Send all events**. "All events" also includes event types added in the future.
5. Optionally add **Custom headers**, such as `X-Api-Key`, if your server expects them.
6. Save. Copy the **Signing secret** from the endpoint page.

A workspace can have up to 20 endpoints. Each endpoint can have up to 20 custom headers. Custom headers cannot replace the signing or transport headers that Workstation sets.

## Events

Each object that can be customized in [Objects](https://workstation.repzo.com/docs/administration/objects) sends `.created`, `.updated`, and `.deleted` events, for example `deal.created`. Some objects also send more specific events:

| Object | Extra events |
|---|---|
| Contact | `contact.stage_changed`, `contact.owner_changed`, `contact.loyalty_points_changed`, `contact.loyalty_tier_changed` |
| Account | `account.stage_changed`, `account.owner_changed` |
| Deal | `deal.stage_changed`, `deal.won`, `deal.lost`, `deal.owner_changed` |
| Ticket | `ticket.status_changed`, `ticket.assigned` |
| Order | `order.stage_changed` |
| Listing | `listing.stage_changed` |
| Subscription | `subscription.status_changed`, `subscription.paused`, `subscription.resumed` |
| Price offer | `price_offer.sent`, `price_offer.status_changed` |
| Invoice | `invoice.finalized`, `invoice.voided`, `invoice.payment_recorded`, `invoice.paid`, `invoice.payment_removed` |
| Form | `form.published`, `form.archived` |
| Activity | `activity.completed` |
| Campaign | `campaign.status_changed` |

Products only send the three standard events. The event picker shows a short description of every event.

## What a delivery looks like

Every request body is a JSON event:

```json
{
  "id": "evt_...",
  "object": "event",
  "type": "deal.updated",
  "apiVersion": "2026-10-01",
  "createdAt": "2026-10-13T09:30:00.000Z",
  "orgId": "...",
  "test": false,
  "actor": { "type": "user", "id": "..." },
  "data": {
    "object": { "id": "...", "name": "Renewal 2027", "amount": "1200.00" },
    "previousAttributes": { "amount": "1000.00" }
  }
}
```

- `data.object` uses the same shape as the record in the [public REST API](https://workstation.repzo.com/docs/configuration/developer-api), so the same parsing code works for both.
- `previousAttributes` appears on updates and holds the old values of the fields that changed.
- Payment events also include `data.payment`; loyalty events include `data.loyalty`.

Delivery order is not guaranteed. Use `createdAt` and the event `id` to sort and de-duplicate.

## Verify the signature

Webhooks follow the [Standard Webhooks](https://www.standardwebhooks.com) scheme, so any Standard Webhooks or Svix library can verify them. Each request has three headers:

| Header | Value |
|---|---|
| `webhook-id` | The event id. It stays the same across retries, so use it to ignore duplicates. |
| `webhook-timestamp` | Unix time in seconds |
| `webhook-signature` | `v1,<base64 HMAC-SHA256>` |

To check a request yourself, compute an HMAC-SHA256 over `{webhook-id}.{webhook-timestamp}.{raw body}`, using the base64 part of your `whsec_` secret as the key, and compare it to the signature. Reject requests with an old timestamp.

### Rotate the secret

On the endpoint page, click **Rotate secret**. Choose **Keep old secret 24 hours** to update your server without failed checks (both signatures are sent during that time), or **Expire old secret now**.

## Retries and failures

Your server should answer with any `2xx` status within 15 seconds. Otherwise Workstation retries the delivery after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, and 10 hours (8 attempts in total).

If every delivery to an endpoint fails for 5 days, Workstation disables the endpoint and notifies admins. To recover:

1. Fix the endpoint and turn **Enabled** back on.
2. Under **Retry failed events**, choose a date and click **Retry failed** to send what the endpoint missed.

Deliveries also pause while the workspace is suspended for billing.

## Test and debug

- **Send a test event** on the endpoint page sends a sample event with `"test": true`. Test events are not retried and never count toward disabling.
- The endpoint's **Deliveries** list filters by **All**, **Succeeded**, **Failed**, and **Retrying**. Open a delivery to see the request headers and body, every attempt, and your server's response. Click **Resend** to send it again.
- The **Event log** tab lists every event, which record triggered it, and which endpoints received it.

Events, deliveries, and attempts are kept for 30 days.

## Webhooks in sandboxes

A [sandbox](https://workstation.repzo.com/docs/administration/sandboxes) can have its own webhook endpoints, so you can test an integration against test data before connecting it to your live workspace.

## Related documentation

- [Developer API and CLI](https://workstation.repzo.com/docs/configuration/developer-api)
- [Workflows](https://workstation.repzo.com/docs/administration/workflows)
- [Sandboxes](https://workstation.repzo.com/docs/administration/sandboxes)
- [Settings](https://workstation.repzo.com/docs/administration/settings)
