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

# Receive created accounts

> Process account creation events securely and idempotently.

ExuraCopy sends `social.account.created` when a user creates a PAMM or MAM master or investor account from Social Trading. The event allows the CRM to record the new account without polling the API.

> [!NOTE]
>
> This webhook reports accounts created through the Social Trading interface. It is not emitted for an account created by the CRM through the API.

## Prepare the receiver

Implement a public HTTPS URL that:

* Accepts `POST` requests with JSON.
* Reads the body as raw bytes without modifying it before signature verification.
* Validates the timestamp and HMAC signature.
* Processes each `event_id` only once.
* Returns a `2xx` status only after durably storing the event.

## Register the webhook

Use the CRM API key with administrative scope to register the receiver. Only one account webhook can exist per environment.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://social-api.example.com/api/v1/integrations/crm/account-webhook" \
  -H "Authorization: Bearer YOUR_CRM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "primary",
    "url": "https://crm.example.com/webhooks/social-accounts"
  }'
```

The destination must use HTTPS and cannot contain credentials or a URL fragment. A second registration returns `409`.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": 1,
  "name": "primary",
  "url": "https://crm.example.com/webhooks/social-accounts",
  "status": "ACTIVE",
  "created_at": "2026-08-20T13:00:00Z",
  "updated_at": "2026-08-20T13:00:00Z",
  "signing_secret": "<STORE_THIS_SECRET_NOW>",
  "signature_algorithm": "HMAC-SHA256"
}
```

> [!WARNING]
>
> `signing_secret` is returned only by registration and secret rotation. Store it immediately in a secrets manager; `GET /account-webhook` never returns it.

## Manage the registration

| Method | Path | Purpose |
| - | - | - |
| `GET` | `/api/v1/integrations/crm/account-webhook` | Retrieve the current URL, name, and status. |
| `PATCH` | `/api/v1/integrations/crm/account-webhook` | Change `name`, `url`, or `status`. |
| `POST` | `/api/v1/integrations/crm/account-webhook/rotate-secret` | Replace the signing secret and return the new value once. |

Disable delivery without deleting the configuration:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X PATCH "https://social-api.example.com/api/v1/integrations/crm/account-webhook" \
  -H "Authorization: Bearer YOUR_CRM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"DISABLED"}'
```

Use `ACTIVE` to enable delivery again. Rotating the secret invalidates the previous value, so update the receiver's secret storage immediately after rotation.

## Headers

| Header | Purpose |
| - | - |
| `X-Social-Event-Id` | Unique, stable event identifier. |
| `X-Social-Event-Type` | Event type; currently `social.account.created`. |
| `X-Social-Timestamp` | Unix timestamp used to calculate the signature. |
| `X-Social-Signature` | Signature in `sha256=...` format. |
| `Content-Type` | `application/json`. |

## Payload

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "event_id": "cf7a7077-4563-5b29-a239-a2c7feaf39ef",
  "type": "social.account.created",
  "version": 1,
  "occurred_at": "2026-08-20T14:31:18.512000Z",
  "data": {
    "origin": "FRONTEND",
    "engine": "PAMM",
    "account_type": "MASTER",
    "mt5_login": "139682",
    "external_reference": "crm-user-127",
    "metatrader_account_product_id": 4,
    "currency": "USD",
    "platform": "MT5",
    "server": "Broker-MT5",
    "platform_group": "social\\real\\usd",
    "leverage": 100,
    "account_created_at": "2026-08-20T14:31:18.102000Z"
  }
}
```

| Field | Description |
| - | - |
| `event_id` | Event idempotency key. |
| `version` | Contract version; use it to handle future changes. |
| `engine` | `PAMM` or `MAM`. |
| `account_type` | `MASTER` or `INVESTOR`. |
| `mt5_login` | Login of the created MT5 account. |
| `external_reference` | User reference originally sent by the CRM. |
| `metatrader_account_product_id` | Product used to create the account; may be `null`. |
| `server`, `platform_group`, `leverage` | Operational configuration; some values may be `null`. |

Passwords, API keys, and SSO tokens are never included.

## Verify the signature

The signature is calculated over:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
X-Social-Timestamp + "." + raw_request_body
```

Node.js example:

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import crypto from "node:crypto";

export function verifyExuraCopyWebhook(rawBody, headers, secret) {
  const timestamp = headers["x-social-timestamp"];
  const received = headers["x-social-signature"];

  if (!timestamp || !received) return false;

  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(Buffer.concat([Buffer.from(`${timestamp}.`), rawBody]))
    .digest("hex");

  const receivedBuffer = Buffer.from(received);
  const expectedBuffer = Buffer.from(expected);

  return receivedBuffer.length === expectedBuffer.length &&
    crypto.timingSafeEqual(receivedBuffer, expectedBuffer);
}
```

Reject timestamps outside a short window—such as five minutes—to reduce replay attacks. Compare signatures in constant time.

## Idempotency and responses

**1. Verify authenticity**

Validate the timestamp and signature against the original body.

**2. Record event\_id**

Insert the event with a unique constraint. If it already exists, do not create the account again in the CRM.

**3. Apply the change**

Associate `mt5_login` with the user identified by `external_reference`.

**4. Acknowledge receipt**

Respond with `200`, `202`, or `204` after storing the event. The response body is ignored.

ExuraCopy retries connection failures, `408`, `425`, `429`, and `5xx` responses. Other `4xx` responses are treated as permanent failures. Because of retries, the same event may arrive more than once.

## Inspect and retry deliveries

Administrators can inspect webhook delivery state:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -G "https://social-api.example.com/api/v1/integrations/crm/account-webhook/events" \
  -H "Authorization: Bearer YOUR_CRM_API_KEY" \
  --data-urlencode "status=FAILED" \
  --data-urlencode "limit=50"
```

The optional `status` filter accepts `PENDING`, `SENDING`, `RETRY`, `DELIVERED`, or `FAILED`. Continue pagination with the `next_cursor` returned by the API.

Retry a delivery by its numeric database `id`, not by `event_id`:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST \
  "https://social-api.example.com/api/v1/integrations/crm/account-webhook/events/42/retry" \
  -H "Authorization: Bearer YOUR_CRM_API_KEY"
```

An already delivered event returns `409` and cannot be retried.

> [!WARNING]
>
> Account creation in Social Trading is not rolled back when the CRM receiver fails. Monitor the delivery endpoint and use the event listing and retry routes to recover failed deliveries.

> [card] [Previous: Debit funds](https://crm-social.exuratech.com/guides/withdrawals.md)
>
> Review the withdrawal workflow.

> [card] [Next: Idempotency](https://crm-social.exuratech.com/reliability/idempotency.md)
>
> Prevent duplicate operations and events.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.