# Webhooks

## Webhook Setup

Partners are able to register multiple webhook subscriptions per account; these subscriptions must be unique by URL per partner. We do not currently support scoping per webhook subscription, so all webhook events will be sent to every endpoint the partner has registered. Subscription to specific webhook scopes is coming soon.

Follow the steps below to set up webhooks for your Basic Capital integration.

### 1. Add a Webhook Handler

Webhooks do not contain any details on the triggered event. Instead, they provide the event type, entity ID, and information about the owner of the data. Together, these provide the information necessary in order to pull the event details from Basic Capital.

Webhook events will be in the following format:

```json
POST /your/configured/webhook/endpoint
X-BC-Webhook-Validation-Token: "sample_validation_token"

{
  "owner_uuid": "string",
  "scope": "PARTNER | COMPANY | EMPLOYEE",
  "object_uuid": "string",
  "event_type": "EventType",
  "timestamp": "string",
  "request_id": "string"
}
```

**Field definitions**:

| Field | Description |
|  --- | --- |
| `owner_uuid` | The entity this event belongs to; pass this in the `X-Identity-Uuid` header when requesting the data |
| `scope` | The identity type this event belongs to; pass this in the `X-Identity-Type` header when requesting the data |
| `object_uuid` | The actual object the event is referencing; used to fetch specific event |
| `event_type` | The type of event that occurred; we indicate which endpoints owns each event type below |
| `timestamp` | When the communication was sent, not when the event occurred. Used for authenticating the webhook request |
| `request_id` | Unique ID for each webhook message request |


### 2. Webhook Verification

While it is not strictly required, we strongly recommend that partners implement verification on incoming webhooks. Each webhook will carry the following JWT header:

```json
X-BC-Webhook-Validation-Token: "sample_validation_jwt"
```

Verify as follows:

1. Fetch Basic Capital's public key via `GetWebhookValidationContext` ([gRPC](/grpc/partneringressservice#getwebhookvalidationcontext) | [REST](/openapi/partneringressservice/getwebhookvalidationcontext)). We recommend caching this value.
2. Use the public key to decode the JWT, which will have the following format

```json
{
  "requestId": "string",
  "timestamp": "string"
}
```
3. Verify the `timestamp`, which will be in epoch milliseconds. Reject requests older than 5 minutes.
4. Verify the `requestId` is unique.
5. Verify `timestamp` and `requestId` values match what is in the request body.


### 3. Register Your Webhook

1. Register a new webhook at `CreateWebhookSubscription` ([gRPC](/grpc/partneringressservice#createwebhooksubscription) | [REST](/openapi/partneringressservice/createwebhooksubscription))
2. On registration, Basic Capital will send a webhook message to the url with event type `VERIFY_WEBHOOK`. This endpoint must return a 200, or the subscription will not be created.


### 4. Removing Stale Webhook Registrations

Webhooks will be created if the initial `VERIFY_WEBHOOK` event returns a 200. However, if a webhook endpoint begins to fail consistently, it will eventually be deactivated. We recommend that you remove old webhooks for clarity; this will help Basic Capital give the best support for your integration. Webhook subscriptions can be paused, reactivated, or deleted via `SetWebhookSubscriptionStatus` ([gRPC](/grpc/partneringressservice#setwebhooksubscriptionstatus) | [REST](/openapi/partneringressservice/setwebhooksubscriptionstatus)).

You can list your current webhooks at any time at `ListWebhookSubscriptions` ([gRPC](/grpc/partneringressservice#listwebhooksubscriptions) | [REST](/openapi/partneringressservice/listwebhooksubscriptions)).

## Webhook Event Types

New webhook event types may be added in the future. We recommend implementations ignore unknown event types instead of erroring, to ensure they remain compatible.

| Event Type | Description | Scope | Endpoint |
|  --- | --- | --- | --- |
| `VERIFY_WEBHOOK` | Initial verification. Must return a 200. | n/a | n/a |
| `ALERT_ACTION_REQUIRED` | Required action in Basic Capital dashboard | `COMPANY` or `EMPLOYEE` | Log into Basic Capital |
| `ALERT_INFORMATIONAL` | New alert, no action required | `COMPANY` or `EMPLOYEE` | Log into Basic Capital |
| `EMPLOYEE_STATUS_CHANGE` | Change to employee's status | `COMPANY` | `GetEmployeeDetails` |
| `EMPLOYEE_ONBOARDING_STATUS_CHANGE` | Change to employee's onboarding progress | `EMPLOYEE` | `GetEmployeeOnboardingStatus` |
| `CONTRIBUTION_CONFIG_UPDATED` | Update to employee's contributions | `EMPLOYEE` | `GetEmployeeContributionRates` |
| `COMPANY_ONBOARDING_STATUS_CHANGE` | Async updates during company onboarding | `COMPANY` | `GetCompanyOnboardingStatus` |
| `PLAN_CONFIGURATION_STATUS_CHANGE` | Updates to plan configuration status | `COMPANY` | `GetCompanyOnboardingStatus` |


## Webhook API documentation

### CreateWebhookSubscription

Create a new webhook subscription. Will fail if the partner already has a subscription with this URL.

```protobuf
service PartnerIngressService {
  rpc CreateWebhookSubscription(CreateWebhookSubscriptionRequest) returns (CreateWebhookSubscriptionResponse);
}

message CreateWebhookSubscriptionRequest {
  string url = 1;
}

message CreateWebhookSubscriptionResponse {
  WebhookSubscription webhook_subscription = 1;
}
```

**Data types:** [WebhookSubscription](/integration-guide/webhooks#webhooksubscription)

**REST:** [`POST /v1/partner-rpc/com.basiccapital.systems.ingress.partner.PartnerIngressService/CreateWebhookSubscription`](/openapi/partneringressservice/createwebhooksubscription)

### GetWebhookSubscription

```protobuf
service PartnerIngressService {
  rpc GetWebhookSubscription(GetWebhookSubscriptionRequest) returns (GetWebhookSubscriptionResponse);
}

message GetWebhookSubscriptionRequest {
  string uuid = 1;
}

message GetWebhookSubscriptionResponse {
  optional WebhookSubscription webhook_subscription = 1;
}
```

**Data types:** [WebhookSubscription](/integration-guide/webhooks#webhooksubscription)

**REST:** [`POST /v1/partner-rpc/com.basiccapital.systems.ingress.partner.PartnerIngressService/GetWebhookSubscription`](/openapi/partneringressservice/getwebhooksubscription)

### ListWebhookSubscriptions

Returns all subscriptions for a partner. Includes paused subscriptions.

```protobuf
service PartnerIngressService {
  rpc ListWebhookSubscriptions(ListWebhookSubscriptionsRequest) returns (ListWebhookSubscriptionsResponse);
}

message ListWebhookSubscriptionsRequest {}

message ListWebhookSubscriptionsResponse {
  repeated WebhookSubscription webhook_subscriptions = 1;
}
```

**Data types:** [WebhookSubscription](/integration-guide/webhooks#webhooksubscription)

**REST:** [`POST /v1/partner-rpc/com.basiccapital.systems.ingress.partner.PartnerIngressService/ListWebhookSubscriptions`](/openapi/partneringressservice/listwebhooksubscriptions)

### UpdateWebhookSubscription

Update the URL for a subscription. Errors if the URL is already registered.

```protobuf
service PartnerIngressService {
  rpc UpdateWebhookSubscription(UpdateWebhookSubscriptionRequest) returns (UpdateWebhookSubscriptionResponse);
}

message UpdateWebhookSubscriptionRequest {
  string uuid = 1;
  string url = 2;
}

message UpdateWebhookSubscriptionResponse {
  WebhookSubscription webhook_subscription = 1;
}
```

**Data types:** [WebhookSubscription](/integration-guide/webhooks#webhooksubscription)

**REST:** [`POST /v1/partner-rpc/com.basiccapital.systems.ingress.partner.PartnerIngressService/UpdateWebhookSubscription`](/openapi/partneringressservice/updatewebhooksubscription)

### SetWebhookSubscriptionStatus

Pause, reactivate, or delete a subscription.

```protobuf
service PartnerIngressService {
  rpc SetWebhookSubscriptionStatus(SetWebhookSubscriptionStatusRequest) returns (SetWebhookSubscriptionStatusResponse);
}

message SetWebhookSubscriptionStatusRequest {
  string uuid = 1;

  enum StatusUpdate {
    UNKNOWN_STATUS_UPDATE = 0;
    REACTIVATE = 1;
    PAUSE = 2;
    DELETE = 3;
  }
  StatusUpdate status_update = 2;
}

message SetWebhookSubscriptionStatusResponse {
  WebhookSubscription webhook_subscription = 1;
}
```

**Data types:** [WebhookSubscription](/integration-guide/webhooks#webhooksubscription)

**REST:** [`POST /v1/partner-rpc/com.basiccapital.systems.ingress.partner.PartnerIngressService/SetWebhookSubscriptionStatus`](/openapi/partneringressservice/setwebhooksubscriptionstatus)

### GetWebhookValidationContext

Fetch the public key for webhook signature validation. Cache this value.

```protobuf
service PartnerIngressService {
  rpc GetWebhookValidationContext(GetWebhookValidationContextRequest) returns (GetWebhookValidationContextResponse);
}

message GetWebhookValidationContextRequest {}

message GetWebhookValidationContextResponse {
  string public_key = 1;
}
```

**REST:** [`POST /v1/partner-rpc/com.basiccapital.systems.ingress.partner.PartnerIngressService/GetWebhookValidationContext`](/openapi/partneringressservice/getwebhookvalidationcontext)

### WebhookSubscription

Partner-facing webhook subscription. DEV_TEST is surfaced as ACTIVE. DELETED is
only ever returned in the immediate response to a SetWebhookSubscriptionStatus
delete; Get/List exclude DELETED subscriptions.

```protobuf
message WebhookSubscription {
  string uuid = 1;
  string url = 2;

  enum Status {
    UNKNOWN_STATUS = 0;
    ACTIVE = 1;
    ERRORED = 2;
    PAUSED = 3;
    DELETED = 4;
  }
  Status status = 3;

  optional string notes = 4;
}
```