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

# Webhook Events

> Webhook event types, payload structure, signature verification, retries, and delivery guarantees for MasterID.

MasterID delivers HTTPS webhook events to one public URL registered in your Developer Console. Use webhooks to receive real-time completion notifications for verifications and collections instead of polling.

For setup instructions including how to register a URL and save your signing secret, see [Webhooks](https://docs.masterid.co.ug/guides/webhooks.md).

## Supported events

| Event | Description |
| - | - |
| `verification.completed` | A verification reached a final state (legitimate, fake, or failed). |
| `collection.completed` | A payment collection reached a final state. |

## Event headers

Every webhook request includes these headers.

**X-MasterID-Event-ID** `string` *required*

Unique identifier for this event delivery. Use it to deduplicate deliveries.

**X-MasterID-Timestamp** `string` *required*

Unix timestamp in seconds when the event was sent.

**X-MasterID-Signature** `string` *required*

Hex-encoded HMAC SHA-256 signature of the payload, computed with your signing secret.

## Payload structure

All events share the same envelope.

**id** `string` *required*

The event ID, matching `X-MasterID-Event-ID`.

**event** `string` *required*

The event type, for example `verification.completed`.

**data** `object` *required*

Event-specific data. Do not invent fields for event types beyond what is documented.

### verification.completed

**data** `object` *required*

**properties**

**sessionId** `string` *required*

The verification session ID (6-12 digits).

**status** `string` *required*

Final status: `legitimate`, `fake`, or `failed`.

**charged** `number` *required*

Amount charged in UGX. Failed checks are not charged.

> [!NOTE]
>
> The `collection.completed` data shape is not documented beyond the envelope. Parse `data` safely; do not assume specific keys.

## Signature verification

Verify every webhook before trusting its payload. Compute the HMAC SHA-256 signature using your signing secret over the string `timestamp + "." + rawBody`. Compare the result in constant time with the `X-MasterID-Signature` header.

> [!WARNING]
>
> Reject payloads with timestamps older than 5 minutes. Parse JSON only after verification succeeds.

### Verification steps

**1. Extract headers**

Read `X-MasterID-Timestamp`, `X-MasterID-Signature`, and the raw request body as a string.

**2. Check timestamp age**

Reject the request if the timestamp is more than 5 minutes old.

**3. Compute HMAC**

Compute `HMAC-SHA256(signingSecret, timestamp + "." + rawBody)` and hex-encode the digest.

**4. Compare signatures**

Compare the computed signature with `X-MasterID-Signature` in constant time. If they do not match, reject the request.

**5. Deduplicate**

Track processed `X-MasterID-Event-ID` values. Ignore repeats.

**6. Accept**

Return any HTTP 2xx after the event is accepted durably.

## Retry schedule

Delivery is at-least-once. Failed deliveries are retried up to five total attempts with increasing delays of approximately 1, 2, 4, and 8 minutes, plus scheduler delay. You can inspect each attempt's HTTP status in API History.

Completion notifications and background retries require the published release.

```bash cURL theme={null}
curl -s -X POST "https://masterid.me/api/public/v1/verifications" \
  -H "X-API-Key: $MASTERID_API_KEY" \
  -H "X-API-Secret: $MASTERID_API_SECRET" \
  -H "Idempotency-Key: your-unique-request-id" \
  -H "Content-Type: application/json" \
  -d '{
    "product": "tin",
    "fields": {"tin": "<APPROVED_TIN>"},
    "consentConfirmed": true,
    "consentText": "<EXACT_CONFIRMED_TEXT>"
  }'
```

```javascript Node.js theme={null}
import { createHmac, timingSafeEqual } from "crypto";

function verifyWebhook(rawBody, headers, signingSecret) {
  const timestamp = headers["x-masterid-timestamp"];
  const signature = headers["x-masterid-signature"];
  const eventId = headers["x-masterid-event-id"];

  const now = Math.floor(Date.now() / 1000);
  if (!timestamp || now - parseInt(timestamp, 10) > 300) {
    throw new Error("Webhook timestamp too old");
  }

  const expected = createHmac("sha256", signingSecret)
    .update(timestamp + "." + rawBody)
    .digest("hex");

  const expectedBuf = Buffer.from(expected, "utf8");
  const signatureBuf = Buffer.from(signature, "utf8");
  if (
    expectedBuf.length !== signatureBuf.length ||
    !timingSafeEqual(expectedBuf, signatureBuf)
  ) {
    throw new Error("Webhook signature mismatch");
  }

  // Deduplicate by eventId after verification
  const payload = JSON.parse(rawBody);
  return { eventId, payload };
}
```

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

def verify_webhook(raw_body: bytes, headers: dict, signing_secret: str):
    timestamp = headers.get("x-masterid-timestamp")
    signature = headers.get("x-masterid-signature")
    event_id = headers.get("x-masterid-event-id")

    now = int(time.time())
    if not timestamp or now - int(timestamp) > 300:
        raise ValueError("Webhook timestamp too old")

    expected = hmac.new(
        signing_secret.encode("utf-8"),
        (timestamp + "." + raw_body.decode("utf-8")).encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        raise ValueError("Webhook signature mismatch")

    payload = json.loads(raw_body)
    return {"event_id": event_id, "payload": payload}
```

```json verification.completed theme={null}
{
  "id": "<EVENT_ID>",
  "event": "verification.completed",
  "data": {
    "sessionId": "<SESSION_ID>",
    "status": "legitimate",
    "charged": 1500
  }
}
```

```json collection.completed theme={null}
{
  "id": "<EVENT_ID>",
  "event": "collection.completed",
  "data": {}
}
```


## Related topics

- [Receive webhooks](https://docs.masterid.co.ug/guides/webhooks.md)
- [Go-live checklist](https://docs.masterid.co.ug/guides/go-live-checklist.md)
- [Collect mobile money payments](https://docs.masterid.co.ug/guides/payment-collection.md)
- [API Overview](https://docs.masterid.co.ug/api-reference/overview.md)
- [MasterID Integration API](https://docs.masterid.co.ug/index.md)


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