Webhook Handlers enable apps to receive incoming requests from third-party services.
Unlike server functions, which are used to send requests,
Webhook Handlers are designed to listen for and process external requests. They
can be used to sync data into Attio or respond with on-demand data to third-party
services, making them essential for integrating external systems and automating workflows.
Example: Receiving prospect updated webhook from email sequencing API
You can use a webhook handler to receive a prospect updated event from the email sequencing API and update the corresponding record in Attio.
To add a webhook handler to your app;
- create a file with the
.webhook.ts
suffix.
- The file must be placed under the
src/webhooks
directory, and it must export default
an async function.
- The function must take an HTTP
Request
argument
and must return an HTTP Response
.
webhooks/prospect-updated.webhook.ts
import {attioFetch} from "attio/server"
type WebhookBody = {
event: "prospect_updated"
data: {
prospectId: string
prospectName: string
prospectEmail: string
}
}
export default async function handleProspectUpdate(req: Request) {
const body: WebhookBody = await req.json()
const {prospectEmail, prospectName} = body.data
// ℹ️ Assert endpoint will update Attio record details
// or create a new record if it doesn't exist
try {
await attioFetch({
path: `/objects/people/records`,
method: "PUT",
queryParams: {
matching_attributes: "email_addresses",
},
body: {
data: {
name: {
full_name: prospectName,
},
email_addresses: [
{
email: prospectEmail,
},
],
},
},
})
return new Response(null, {status: 200})
} catch (error) {
console.error(error)
return new Response(null, {status: 500})
}
}
This webhook handler calls the Assert Record endpoint to create or update a record in Attio.
Whatever status code your handler returns will be passed back to the third-party service to indicate success or failure.
If your handler doesn’t return a Response
object, the App SDK will respond with a 500 status code by default.
Defining a webhook handler isn’t enough to make it usable. You’ll need to enable it programmatically and register it with the third-party service.
You can do this from any server function, but most of the time you’ll want to do it inside the connection-added
event.
The connection-added
event runs whenever an Attio user connects to a third-party service through your app.
If your event handler completes without throwing an error, the connection will be saved.
If it throws, the user will be asked to authenticate again.
Be sure to test your connection-added
events carefully. If something goes wrong, users won’t be
able to connect to third-party services.
Now back to our example. We are going to add connection-added
event handler to our app.
- The file must live in
src/events/connection-added.event.ts
- The file must
export default
an async function that takes a { connection: Connection }
argument.
events/connection-added.event.ts
import type {Connection} from "attio/server"
import {createWebhookHandler, updateWebhookHandler} from "attio/server"
export default async function connectionAdded({connection}: {connection: Connection}) {
// ℹ️ The filename must match the file in src/webhooks, but without the suffix
const handler = await createWebhookHandler({fileName: "prospect-updated"})
const authorizationToken = connection.value
const response = await fetch("https://emailsequencingtool.com/api/v1/webhooks", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${authorizationToken}`,
},
body: JSON.stringify({
name: handler.id,
url: handler.url,
event: "lead.processed",
}),
})
if (!response.ok) {
// ℹ️ Because we throw an error, the connection will not be saved
// and user will be asked to authenticate again
throw new Error(`Failed to register webhook: ${response.statusText}`)
}
const webhook = await response.json()
// ℹ️ Save the external webhook ID so we
// can delete it when the connection is removed
await updateWebhookHandler(handler.id, {
externalWebhookId: webhook.webhook_id,
})
}
And that’s it! now whenever a prospect update webhook is received, the prospect-updated
webhook handler will be called.
You can use the externalWebhookId
to delete the webhook from the third-party service when the
connection is removed inside the connection-removed
event.