# Working with Webhooks

Receive real-time event notifications from the Telgea API.

> Enterprise API · https://developer.telgea.com/docs/enterprise/guides/webhooks

## Webhooks [#webhooks]

Telgea delivers real-time event notifications via HTTP webhooks, allowing your application to react to events like plan provisioning, number porting updates, and SIM replacements.

## Setting up an endpoint [#setting-up-an-endpoint]

An endpoint is a URL plus the events it receives. Admins create endpoints in the Telgea Portal under **Integrations → Webhooks**, and reveal the signing secret there. If your organization has no Portal access, Telgea support sets endpoints up for you. There is no API for managing endpoints.

Your organization has one signing secret, shared by all of its endpoints.

## Payload Format [#payload-format]

All webhook events share a common envelope:

```json schema=WebhookEnvelope
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "eventType": "PortInAccepted",
  "timestamp": "2026-03-25T14:30:00.000Z",
  "organizationId": "b5e2d7f3-a1c4-4e89-9f6b-3d8a7c5e1024",
  "data": { }
}
```

| Field            | Type     | Description                                                                                                  |
| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------ |
| `id`             | `string` | Identifies this delivery to one endpoint; every retry of it carries the same `id`, so use it to de-duplicate |
| `eventType`      | `string` | The event type                                                                                               |
| `timestamp`      | `string` | When Telgea dispatched the event: ISO 8601 in UTC, with milliseconds                                         |
| `organizationId` | `string` | The organization the event belongs to                                                                        |
| `data`           | `object` | Event-specific payload                                                                                       |

## How a delivery works [#how-a-delivery-works]

## HMAC Signature Verification [#hmac-signature-verification]

All webhook payloads are signed using HMAC-SHA256 to ensure authenticity and prevent tampering. Verify every incoming webhook before processing it.

Each delivery includes these headers:

| Header                | Description                                               |
| --------------------- | --------------------------------------------------------- |
| `X-Webhook-Signature` | Hex-encoded HMAC-SHA256 signature of the raw request body |
| `X-Webhook-Id`        | Unique delivery ID (matches `id` in the payload)          |
| `X-Webhook-Timestamp` | Matches `timestamp` in the payload                        |

### Verification steps [#verification-steps]

1. Read the raw request body as a string (do not parse it first)
2. Compute HMAC-SHA256 of the raw body using your webhook signing secret
3. Hex-encode the result
4. Compare it to the `X-Webhook-Signature` header using a constant-time comparison

```javascript
import { createHmac, timingSafeEqual } from "crypto";

function verifyWebhook(rawBody, signature, secret) {
  const expected = createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  const sigBuf = Buffer.from(signature);
  const expBuf = Buffer.from(expected);
  if (sigBuf.length !== expBuf.length) return false;
  return timingSafeEqual(sigBuf, expBuf);
}
```

```python
import hmac
import hashlib

def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)
```

**Important:** Always use constant-time comparison functions (`timingSafeEqual`, `hmac.compare_digest`) to prevent timing attacks.

## Delivery and Retries [#delivery-and-retries]

* **Timeout**: Each delivery attempt times out after 30 seconds
* **Success**: HTTP 2xx status codes are treated as successful delivery
* **Retries**: HTTP 5xx responses, 429 (Too Many Requests), timeouts and network errors are retried every second, up to 50 attempts
* **Paused, not dropped**: after 50 failed attempts the delivery is paused and waits for Telgea to resume it. Contact support once your endpoint is healthy again
* **Terminal failures**: All other HTTP 4xx responses are treated as permanent failures and will not be retried

Because a retry repeats the same `id`, your endpoint may receive one delivery more than once. Make processing idempotent on `id`.

## Events [#events]

### User Plans [#user-plans]

| Event                                                 | Description                        |
| ----------------------------------------------------- | ---------------------------------- |
| [`UserPlanCreated`](https://developer.telgea.com/docs/enterprise/webhooks/user-plan-created) | Plan created, provisioning started |
| [`UserPlanActive`](https://developer.telgea.com/docs/enterprise/webhooks/user-plan-active)   | Plan provisioned and active        |

### Port-Ins [#port-ins]

| Event                                                 | Description                                              |
| ----------------------------------------------------- | -------------------------------------------------------- |
| [`PortInSubmitted`](https://developer.telgea.com/docs/enterprise/webhooks/port-in-submitted) | Submitted to the losing carrier                          |
| [`PortInAccepted`](https://developer.telgea.com/docs/enterprise/webhooks/port-in-accepted)   | Accepted by the losing carrier                           |
| [`PortInRejected`](https://developer.telgea.com/docs/enterprise/webhooks/port-in-rejected)   | Rejected by the losing carrier or by provider validation |
| [`PortInSucceeded`](https://developer.telgea.com/docs/enterprise/webhooks/port-in-succeeded) | Number ported successfully                               |
| [`PortInFailed`](https://developer.telgea.com/docs/enterprise/webhooks/port-in-failed)       | Failed at any stage, or cancelled                        |

See the [Transfer Number to Telgea](https://developer.telgea.com/docs/enterprise/guides/port-in) guide for the full porting lifecycle and failure codes.

### SIM Replacement [#sim-replacement]

| Event                                        | Description                               |
| -------------------------------------------- | ----------------------------------------- |
| [`SimReplaced`](https://developer.telgea.com/docs/enterprise/webhooks/sim-replaced) | New eSIM provisioned, old SIM deactivated |

### SIM Lifecycle [#sim-lifecycle]

| Event                                                       | Description                             |
| ----------------------------------------------------------- | --------------------------------------- |
| [`NumberSimInstalled`](https://developer.telgea.com/docs/enterprise/webhooks/number-sim-installed) | eSIM installed on the customer's device |
