Event functions
Run function logic automatically when Tenklo connection lifecycle events happen.
Event functions run automatically when Tenklo 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 |
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.
Create a setup function
Add a file under the integration's on-events/ folder:
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:
import './salesforce/on-events/post-connection-setup';Create a connection validation function
Add a file under the integration's on-events/ folder:
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:
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.
Compare the locked value in metadata to a live value from the provider API. Do not compare connection_config fields: on reconnect, Tenklo upserts the new credentials and overwrites connection_config before validate-connection runs, so config already reflects the account the user just authenticated with.
Create a cleanup function
If the provider webhook is registered per connection, clean it up before the Tenklo connection is deleted:
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:
import './salesforce/on-events/pre-connection-cleanup';Test and deploy
Dry run an event function against an existing connection:
nango dryrun post-connection-setup '<CONNECTION-ID>' -e devDeploy your functions:
nango deployEvent functions run automatically when their configured event occurs. Your app does not trigger them directly.