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

# Receive real-time events with Safeclose webhooks

> Safeclose accepts signed webhook events from Stripe, Plaid, and SendGrid, and posts a signed event to your vault endpoint when a signer completes a document.

Safeclose integrates with external services through webhooks in two directions: Stripe, Plaid, and SendGrid push events **to** Safeclose when something happens on their end, and Safeclose pushes a `signer_document_signed` event **to your vault endpoint** each time a signer completes a document. Every inbound webhook is verified before any work is done, and all processing happens asynchronously so your signing workflows are never blocked.

## Inbound webhooks

Safeclose exposes three public endpoints that third-party services call. Each endpoint enforces its own signature or token check.

<Warning>
  All webhook receiver URLs must be publicly accessible HTTPS endpoints. Safeclose cannot reach endpoints behind a firewall or on `localhost` unless you use a tunneling tool such as ngrok during development.
</Warning>

### Stripe subscription events

**`POST /api/webhooks/stripe`**

Stripe calls this endpoint when subscription-related events occur — for example when a checkout completes, when a subscription is updated, or when a payment fails. Safeclose uses these events to keep your organization's billing status in sync.

**Verification**: Safeclose reads the `stripe-signature` header and validates it against your webhook signing secret. Requests with a missing or invalid signature are rejected with `400`. To set this up, create a webhook endpoint in your [Stripe Dashboard](https://dashboard.stripe.com/webhooks) pointing at `https://your-app.com/api/webhooks/stripe`, then copy the signing secret into your Safeclose configuration.

Stripe events that Safeclose processes:

| Event                           | What Safeclose does                                                             |
| ------------------------------- | ------------------------------------------------------------------------------- |
| `customer.subscription.created` | Creates or updates your billing record and sets the plan tier.                  |
| `customer.subscription.updated` | Syncs subscription status and current period end date.                          |
| `customer.subscription.deleted` | Marks the subscription as canceled and resets tier to Free.                     |
| `checkout.session.completed`    | Links the Stripe customer to your organization and activates the selected plan. |
| `invoice.paid`                  | Confirms active status and refreshes the current period end.                    |
| `invoice.payment_failed`        | Marks your billing account as past due.                                         |

**Example payload Safeclose receives from Stripe:**

```json theme={null}
{
  "id": "evt_1ExampleStripeEvent",
  "type": "customer.subscription.updated",
  "livemode": true,
  "data": {
    "object": {
      "id": "sub_1ExampleSub",
      "status": "active",
      "customer": "cus_ExampleCustomer",
      "metadata": {
        "organizationId": "org_01example",
        "safeclose_plan_tier": "PRO"
      }
    }
  }
}
```

**Example response Safeclose returns to Stripe:**

```json theme={null}
{ "received": true }
```

<Note>
  Configure your webhook endpoint in the Stripe Dashboard to send at minimum: `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted`, `checkout.session.completed`, `invoice.paid`, and `invoice.payment_failed`.
</Note>

***

### Plaid item update events

**`POST /api/webhooks/plaid`**

Plaid calls this endpoint when a linked item's status changes — for example when new transactions are available or when an item requires re-authentication.

**Verification**: Safeclose verifies the `Plaid-Verification` JWT header using Plaid's webhook verification key API and confirms it matches the SHA-256 hash of the request body. Requests that fail verification are rejected with `401`. Plaid sends this header automatically; you do not need to configure anything on the Plaid side beyond pointing your webhook URL at this endpoint in the [Plaid Dashboard](https://dashboard.plaid.com/).

**Example payload Plaid sends:**

```json theme={null}
{
  "webhook_type": "TRANSACTIONS",
  "webhook_code": "SYNC_UPDATES_AVAILABLE",
  "item_id": "eVBnVMp7zxNmBoTgB3K"
}
```

**Example response Safeclose returns to Plaid:**

```json theme={null}
{ "received": true }
```

<Tip>
  See [Link bank accounts with Plaid](/integrations/plaid) for the full Plaid Link flow and how to retrieve a user's linked item status.
</Tip>

***

### SendGrid inbound email

**`POST /api/webhooks/sendgrid`**

SendGrid Inbound Parse calls this endpoint when an email arrives at your configured inbound address. Safeclose enqueues the message for processing by your organization's integration worker.

**Verification**: Safeclose optionally checks a `token` query parameter. If your workspace administrator has configured a token, requests that omit or provide the wrong token are rejected with `401`. Add `?token=<your-token>` to the webhook URL you register in your SendGrid Inbound Parse settings.

The endpoint accepts both `multipart/form-data` (SendGrid's default Inbound Parse format) and `application/json`.

**Fields extracted from each inbound message:**

| Field     | Description                                  |
| --------- | -------------------------------------------- |
| `to`      | The recipient address the email was sent to. |
| `subject` | The email subject line.                      |
| `text`    | The plain-text body of the email.            |

**Example payload (JSON format):**

```json theme={null}
{
  "to": "compliance@inbox.yourdomain.com",
  "subject": "Signing package received",
  "text": "Package SC-4892 has been received and is pending review."
}
```

***

## Outbound vault events

When a signer completes a document, Safeclose can POST a `signer_document_signed` event to a URL of your choice. This lets you trigger downstream workflows — for example, storing the signed artifact in your own vault system.

Your workspace administrator configures the vault webhook URL on the server. When the URL is set, Safeclose sends the following JSON body via `POST` immediately after the e-signature is recorded:

```json theme={null}
{
  "type": "signer_document_signed",
  "signingOrganizationId": "org_01example",
  "signingId": "8842",
  "documentId": "3317",
  "clerkUserId": "user_01example",
  "firstESign": true
}
```

| Field                   | Type    | Description                                                                            |
| ----------------------- | ------- | -------------------------------------------------------------------------------------- |
| `type`                  | string  | Always `"signer_document_signed"`.                                                     |
| `signingOrganizationId` | string  | The ID of the organization that owns the signing package.                              |
| `signingId`             | string  | The ID of the signing package.                                                         |
| `documentId`            | string  | The ID of the document that was signed.                                                |
| `clerkUserId`           | string  | The authenticated user who recorded the signature.                                     |
| `firstESign`            | boolean | `true` if this is the first time this signer recorded an e-signature on this document. |

**Authentication**: Safeclose sends an `Authorization: Bearer <secret>` header when a vault webhook secret is configured. Your endpoint should validate this token before processing the event.

<Note>
  Your vault webhook endpoint must respond with `2xx` within a reasonable timeout. If Safeclose receives a non-`2xx` response, the job is retried automatically.
</Note>

***

## Related pages

<CardGroup cols={2}>
  <Card title="Stripe billing" icon="credit-card" href="/integrations/stripe">
    Manage subscriptions and start a checkout session for your organization.
  </Card>

  <Card title="Plaid bank linking" icon="building-columns" href="/integrations/plaid">
    Link bank accounts during the signing flow using Plaid.
  </Card>
</CardGroup>
