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

# Event functions

> Run function logic automatically when Nango connection lifecycle events happen.

Event functions run automatically when Nango reaches a connection lifecycle event. They are defined with `createOnEvent()`.

Use them to validate credentials, register provider webhooks, seed connection-specific metadata, or clean up external resources before a connection is deleted.

## Choose the lifecycle event

Supported events:

| Event                      | When it runs                              | Common use                                                                                                     |
| -------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `post-connection-creation` | Immediately after a connection is created | Register provider webhooks, seed metadata, run setup checks                                                    |
| `validate-connection`      | During connection creation and reconnect  | Reject invalid credentials, missing provider permissions, or policy violations (e.g. same-account enforcement) |
| `pre-connection-deletion`  | Before a connection is deleted            | Delete provider webhook subscriptions or external resources                                                    |

<Accordion title="For agents">
  Before generating an event function, identify the lifecycle event, provider endpoint, required metadata, and desired failure behavior.

  For `validate-connection`, throwing rejects the auth attempt. On first connect, the connection is deleted. On reconnect, the connection is marked as an auth error (`refresh_exhausted`) and the user must reconnect explicitly. For cleanup functions, make the provider call idempotent because the external resource may already be gone.

  For same-account enforcement on reconnect, store the provider account identifier in **connection metadata** during `post-connection-creation`, then compare it to a live identity API call in `validate-connection`. Do not compare `connection_config` fields: reconnect upserts new credentials before validation runs, so config values already reflect the newly authenticated account.
</Accordion>

## Create a setup function

Add a file under the integration's `on-events/` folder:

```typescript salesforce/on-events/post-connection-setup.ts theme={null}
import { createOnEvent } from 'nango';
import * as z from 'zod';

const Metadata = z.object({
    organization_id: z.string().optional(),
    webhookId: z.string().optional()
});

export default createOnEvent({
    description: 'Register a Salesforce webhook after a successful connection',
    event: 'post-connection-creation',
    metadata: Metadata,
    exec: async (nango) => {
        const response = await nango.post({
            endpoint: '/webhooks',
            data: {
                url: 'https://api.myapp.com/webhooks/salesforce',
                events: ['contact.updated']
            }
        });

        await nango.setMetadata({
            webhookId: response.data.id
        });
    }
});
```

Import it from `index.ts`:

```typescript index.ts theme={null}
import './salesforce/on-events/post-connection-setup';
```

## Create a connection validation function

Add a file under the integration's `on-events/` folder:

```typescript salesforce/on-events/validate-connection.ts theme={null}
import { createOnEvent } from 'nango';
import * as z from 'zod';

const Metadata = z.object({
    organizationId: z.string().optional(),
    webhookId: z.string().optional()
});

export default createOnEvent({
    description: 'Validate connection against saved organization id to prevent changing account on reconnect',
    event: 'validate-connection',
    metadata: Metadata,
    exec: async (nango) => {
        const metadata = await nango.getMetadata();
        const lockedOrganizationId = metadata?.organizationId;

        const response = await nango.get<{organization_id: string}>({
            endpoint: '/services/oauth2/userinfo'
        });
        const organizationId = response.data?.organization_id;
        if (!organizationId) {
            throw new nango.ActionError('Salesforce connection missing organization_id from /services/oauth2/userinfo');
        }

        // On initial connection, save organization id to connection metadata
        if (!lockedOrganizationId) {
            await nango.setMetadata({organizationId});
            return;
        }

        // On subsequent connections, check if new organization id matches stored value
        if (lockedOrganizationId !== organizationId) {
            throw new nango.ActionError(
                `Salesforce org mismatch: expected ${lockedOrganizationId}, got ${organizationId}`
            );
        }
    }
});
```

Import it from `index.ts`:

```typescript index.ts theme={null}
import './salesforce/on-events/validate-connection';
```

On first connect, validation passes when metadata has no locked org yet; the setup function records `organization_id` after auth succeeds. On reconnect, validation compares the new credentials against the saved org ID and rejects the attempt if they differ.

<Info>
  Compare the locked value in **metadata** to a live value from the provider API. Do not compare `connection_config` fields: on reconnect, Nango upserts the new credentials and overwrites `connection_config` **before** `validate-connection` runs, so config already reflects the account the user just authenticated with.
</Info>

## Create a cleanup function

If the provider webhook is registered per connection, clean it up before the Nango connection is deleted:

```typescript salesforce/on-events/pre-connection-cleanup.ts theme={null}
import { createOnEvent } from 'nango';
import * as z from 'zod';

const Metadata = z.object({
    organization_id: z.string().optional(),
    webhookId: z.string().optional()
});

export default createOnEvent({
    description: 'Delete the Salesforce webhook before connection deletion',
    event: 'pre-connection-deletion',
    metadata: Metadata,
    exec: async (nango) => {
        const metadata = await nango.getMetadata();
        if (!metadata?.webhookId) {
            return;
        }

        await nango.delete({
            endpoint: `/webhooks/${metadata.webhookId}`
        });
    }
});
```

Import it from `index.ts`:

```typescript index.ts theme={null}
import './salesforce/on-events/pre-connection-cleanup';
```

## Test and deploy

Dry run an event function against an existing connection:

```bash theme={null}
nango dryrun post-connection-setup '<CONNECTION-ID>' -e dev
```

Deploy your functions:

```bash theme={null}
nango deploy
```

Event functions run automatically when their configured event occurs. Your app does not trigger them directly.

## Related guides

* [Webhook functions](/guides/functions/webhook-functions)
* [Webhook forwarding](/guides/platform/webhook-forwarding)
* [Storage](/guides/functions/storage)
* [Functions SDK reference](/reference/functions/functions-sdk)
