Announcing the Webhooks API: Manage webhooks programmatically with Server SDKs_
Webhooks are no longer console-only. Create, update, and delete webhooks using the Appwrite Server SDKs and API keys with the new webhooks.read and webhooks.write scopes.

Webhooks have always been a core part of how developers integrate Appwrite into their workflows. Subscribe to an event, receive an HTTP POST, and let your server handle the rest. Until now, managing those webhooks meant logging into the Appwrite Console and configuring them by hand.
Today, we are announcing the Webhooks API, a fully programmable interface for managing webhooks through Server SDKs and API keys.
Why this matters
If you have ever needed to set up webhooks across multiple projects, automate webhook provisioning as part of a deployment pipeline, or manage webhook lifecycles from code, you know the friction of doing it through a UI.
The Webhooks API removes that friction entirely. Webhooks become just another resource you can create, update, and delete programmatically, the same way you already manage databases, functions, and storage buckets.
This is especially valuable for:
- CI/CD pipelines that need to register webhooks as part of environment setup
- Multi-tenant platforms that provision webhooks per customer or workspace
- Migration and seeding scripts that replicate webhook configurations across environments
- Admin dashboards that let non-technical team members manage webhooks without Console access
How it works
The Webhooks API introduces two new API key scopes:
webhooks.readfor listing and retrieving webhookswebhooks.writefor creating, updating, and deleting webhooks
Once your API key has the appropriate scopes, you can manage webhooks through any Appwrite Server SDK. To get started:
- Navigate to Overview > Integration > API keys and create or update an API key with the
webhooks.readandwebhooks.writescopes. - Initialize a Server SDK with your API key.
- Use the
Webhooksservice to manage your webhooks from code.
Create a webhook
The response includes a secret field containing the webhook's signing key. This is the only time the secret is returned, so store it securely right away.
import { Client, Webhooks, ID } from 'node-appwrite';
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>')
.setKey('<YOUR_API_KEY>');
const webhooks = new Webhooks(client);
const result = await webhooks.create({
webhookId: ID.unique(),
url: 'https://example.com/webhook',
name: 'My Webhook',
events: ['users.*.create', 'users.*.update'],
tls: true,
secret: '<SECRET>' // optional
});
console.log(result.secret); // store it now, not returned again
import { Client, Webhooks, ID } from "npm:node-appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>')
.setKey('<YOUR_API_KEY>');
const webhooks = new Webhooks(client);
const result = await webhooks.create({
webhookId: ID.unique(),
url: 'https://example.com/webhook',
name: 'My Webhook',
events: ['users.*.create', 'users.*.update'],
tls: true,
secret: '<SECRET>' // optional
});
console.log(result.secret); // store it now, not returned again
<?php
use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Services\Webhooks;
$client = new Client();
$client
->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
->setProject('<PROJECT_ID>')
->setKey('<YOUR_API_KEY>');
$webhooks = new Webhooks($client);
$result = $webhooks->create(
webhookId: ID::unique(),
url: 'https://example.com/webhook',
name: 'My Webhook',
events: ['users.*.create', 'users.*.update'],
tls: true,
secret: '<SECRET>' // optional
);
echo $result->secret; // store it now, not returned again
from appwrite.client import Client
from appwrite.id import ID
from appwrite.services.webhooks import Webhooks
client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')
webhooks = Webhooks(client)
result = webhooks.create(
webhook_id=ID.unique(),
url='https://example.com/webhook',
name='My Webhook',
events=['users.*.create', 'users.*.update'],
tls=True,
secret='<SECRET>' # optional
)
print(result.secret) # store it now, not returned again
require 'appwrite'
include Appwrite
client = Client.new
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project('<PROJECT_ID>')
.set_key('<YOUR_API_KEY>')
webhooks = Webhooks.new(client)
response = webhooks.create(
webhook_id: ID.unique(),
url: 'https://example.com/webhook',
name: 'My Webhook',
events: ['users.*.create', 'users.*.update'],
tls: true,
secret: '<SECRET>' # optional
)
puts response.secret # store it now, not returned again
using Appwrite;
using Appwrite.Services;
using Appwrite.Models;
Client client = new Client()
.SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
.SetProject("<PROJECT_ID>")
.SetKey("<YOUR_API_KEY>");
Webhooks webhooks = new Webhooks(client);
var result = await webhooks.Create(
webhookId: ID.Unique(),
url: "https://example.com/webhook",
name: "My Webhook",
events: new List<string> {"users.*.create", "users.*.update"},
tls: true,
secret: "<SECRET>" // optional
);
Console.WriteLine(result.Secret); // store it now, not returned again
import 'package:dart_appwrite/dart_appwrite.dart';
Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>')
.setKey('<YOUR_API_KEY>');
Webhooks webhooks = Webhooks(client);
final result = await webhooks.create(
webhookId: ID.unique(),
url: 'https://example.com/webhook',
name: 'My Webhook',
events: ['users.*.create', 'users.*.update'],
tls: true,
secret: '<SECRET>', // optional
);
print(result.secret); // store it now, not returned again
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.services.Webhooks
val client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.setProject("<PROJECT_ID>")
.setKey("<YOUR_API_KEY>")
val webhooks = Webhooks(client)
val result = webhooks.create(
webhookId = ID.unique(),
url = "https://example.com/webhook",
name = "My Webhook",
events = listOf("users.*.create", "users.*.update"),
tls = true,
secret = "<SECRET>" // optional
)
println(result.secret) // store it now, not returned again
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Webhooks;
Client client = new Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.setProject("<PROJECT_ID>")
.setKey("<YOUR_API_KEY>");
Webhooks webhooks = new Webhooks(client);
webhooks.create(
ID.unique(), // webhookId
"https://example.com/webhook", // url
"My Webhook", // name
List.of("users.*.create", "users.*.update"), // events
true, // enabled
true, // tls
null, // authUsername
null, // authPassword
"<SECRET>", // secret (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
System.out.println(result.getSecret()); // store it now, not returned again
})
);
import Appwrite
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.setProject("<PROJECT_ID>")
.setKey("<YOUR_API_KEY>")
let webhooks = Webhooks(client)
let result = try await webhooks.create(
webhookId: ID.unique(),
url: "https://example.com/webhook",
name: "My Webhook",
events: ["users.*.create", "users.*.update"],
tls: true,
secret: "<SECRET>" // optional
)
print(result.secret) // store it now, not returned again
package main
import (
"fmt"
"github.com/appwrite/sdk-for-go/appwrite"
"github.com/appwrite/sdk-for-go/id"
)
func main() {
client := appwrite.NewClient(
appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
appwrite.WithProject("<PROJECT_ID>"),
appwrite.WithKey("<YOUR_API_KEY>"),
)
webhooks := appwrite.NewWebhooks(client)
result, err := webhooks.Create(
id.Unique(),
"https://example.com/webhook",
"My Webhook",
[]string{"users.*.create", "users.*.update"},
appwrite.WithCreateSecret("<SECRET>"), // optional
)
if err != nil {
panic(err)
}
fmt.Println(result.Secret) // store it now, not returned again
}
use appwrite::Client;
use appwrite::id::ID;
use appwrite::services::Webhooks;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new()
.set_endpoint("https://<REGION>.cloud.appwrite.io/v1")
.set_project("<PROJECT_ID>")
.set_key("<YOUR_API_KEY>");
let webhooks = Webhooks::new(&client);
let result = webhooks.create(
ID::unique(), // webhook_id
"https://example.com/webhook", // url
"My Webhook", // name
vec!["users.*.create", "users.*.update"], // events
None, // enabled
Some(true), // tls
None, // auth_username
None, // auth_password
Some("<SECRET>"), // secret (optional)
).await?;
println!("{}", result.secret); // store it now, not returned again
Ok(())
}
Update a webhook
const result = await webhooks.update({
webhookId: '<WEBHOOK_ID>',
name: 'Updated Webhook',
url: 'https://example.com/webhook-v2',
events: ['users.*.create', 'users.*.update', 'users.*.delete'],
tls: true
});
const result = await webhooks.update({
webhookId: '<WEBHOOK_ID>',
name: 'Updated Webhook',
url: 'https://example.com/webhook-v2',
events: ['users.*.create', 'users.*.update', 'users.*.delete'],
tls: true
});
$result = $webhooks->update(
webhookId: '<WEBHOOK_ID>',
name: 'Updated Webhook',
url: 'https://example.com/webhook-v2',
events: ['users.*.create', 'users.*.update', 'users.*.delete'],
tls: true
);
result = webhooks.update(
webhook_id='<WEBHOOK_ID>',
name='Updated Webhook',
url='https://example.com/webhook-v2',
events=['users.*.create', 'users.*.update', 'users.*.delete'],
tls=True
)
response = webhooks.update(
webhook_id: '<WEBHOOK_ID>',
name: 'Updated Webhook',
url: 'https://example.com/webhook-v2',
events: ['users.*.create', 'users.*.update', 'users.*.delete'],
tls: true
)
var result = await webhooks.Update(
webhookId: "<WEBHOOK_ID>",
name: "Updated Webhook",
url: "https://example.com/webhook-v2",
events: new List<string> {"users.*.create", "users.*.update", "users.*.delete"},
tls: true
);
final result = await webhooks.update(
webhookId: '<WEBHOOK_ID>',
name: 'Updated Webhook',
url: 'https://example.com/webhook-v2',
events: ['users.*.create', 'users.*.update', 'users.*.delete'],
tls: true,
);
val result = webhooks.update(
webhookId = "<WEBHOOK_ID>",
name = "Updated Webhook",
url = "https://example.com/webhook-v2",
events = listOf("users.*.create", "users.*.update", "users.*.delete"),
tls = true
)
webhooks.update(
"<WEBHOOK_ID>", // webhookId
"Updated Webhook", // name
"https://example.com/webhook-v2", // url
List.of("users.*.create", "users.*.update", "users.*.delete"), // events
true, // enabled
true, // tls
null, // authUsername
null, // authPassword
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
System.out.println(result);
})
);
let result = try await webhooks.update(
webhookId: "<WEBHOOK_ID>",
name: "Updated Webhook",
url: "https://example.com/webhook-v2",
events: ["users.*.create", "users.*.update", "users.*.delete"],
tls: true
)
result, err := webhooks.Update(
"<WEBHOOK_ID>",
"Updated Webhook",
"https://example.com/webhook-v2",
[]string{"users.*.create", "users.*.update", "users.*.delete"},
)
let result = webhooks.update(
"<WEBHOOK_ID>", // webhook_id
"Updated Webhook", // name
"https://example.com/webhook-v2", // url
vec!["users.*.create", "users.*.update", "users.*.delete"], // events
None, // enabled
Some(true), // tls
None, // auth_username
None, // auth_password
).await?;
Delete a webhook
await webhooks.delete({
webhookId: '<WEBHOOK_ID>'
});
await webhooks.delete({
webhookId: '<WEBHOOK_ID>'
});
$webhooks->delete(
webhookId: '<WEBHOOK_ID>'
);
webhooks.delete(
webhook_id='<WEBHOOK_ID>'
)
webhooks.delete(
webhook_id: '<WEBHOOK_ID>'
)
await webhooks.Delete(
webhookId: "<WEBHOOK_ID>"
);
await webhooks.delete(
webhookId: '<WEBHOOK_ID>',
);
webhooks.delete(
webhookId = "<WEBHOOK_ID>"
)
webhooks.delete(
"<WEBHOOK_ID>", // webhookId
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
System.out.println(result);
})
);
try await webhooks.delete(
webhookId: "<WEBHOOK_ID>"
)
_, err := webhooks.Delete(
"<WEBHOOK_ID>",
)
webhooks.delete(
"<WEBHOOK_ID>",
).await?;
Rotate signing key
By default, calling updateSecret generates a fresh random signing key. You can also pass your own secret (8-256 characters), which is useful for zero-downtime key rotation.
const result = await webhooks.updateSecret({
webhookId: '<WEBHOOK_ID>',
secret: '<SECRET>' // optional
});
const result = await webhooks.updateSecret({
webhookId: '<WEBHOOK_ID>',
secret: '<SECRET>' // optional
});
$result = $webhooks->updateSecret(
webhookId: '<WEBHOOK_ID>',
secret: '<SECRET>' // optional
);
result = webhooks.update_secret(
webhook_id='<WEBHOOK_ID>',
secret='<SECRET>' # optional
)
response = webhooks.update_secret(
webhook_id: '<WEBHOOK_ID>',
secret: '<SECRET>' # optional
)
var result = await webhooks.UpdateSecret(
webhookId: "<WEBHOOK_ID>",
secret: "<SECRET>" // optional
);
final result = await webhooks.updateSecret(
webhookId: '<WEBHOOK_ID>',
secret: '<SECRET>', // optional
);
val result = webhooks.updateSecret(
webhookId = "<WEBHOOK_ID>",
secret = "<SECRET>" // optional
)
webhooks.updateSecret(
"<WEBHOOK_ID>", // webhookId
"<SECRET>", // secret (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
System.out.println(result);
})
);
let result = try await webhooks.updateSecret(
webhookId: "<WEBHOOK_ID>",
secret: "<SECRET>" // optional
)
result, err := webhooks.UpdateSecret(
"<WEBHOOK_ID>",
appwrite.WithUpdateSecretSecret("<SECRET>"), // optional
)
let result = webhooks.update_secret(
"<WEBHOOK_ID>", // webhook_id
Some("<SECRET>") // optional
).await?;
The API supports all the same configuration options available in the Console, including event selection, SSL/TLS certificate verification, and HTTP basic authentication.
Get started
The Webhooks API is available on Appwrite Cloud today. Full documentation, including examples for all supported SDKs, is available on the webhooks documentation page.





