Webhooks
Call your endpoints when something happens.
A webhook sends an HTTP request to a URL of your choice whenever a given event occurs in the membership. Use webhooks to keep other systems in sync with ErtisAuth: create a customer record when a user signs up, clean up data when a user is deleted, post to a chat channel when a role changes.
The webhook object#
{
"_id": "66f1c0d2a4b5c6d7e8f901a0",
"name": "Sync new users to CRM",
"slug": "sync-new-users-to-crm",
"description": "Creates a contact for every new user",
"event": "UserCreated",
"status": "active",
"try_count": 3,
"request": {
"method": "POST",
"url": "https://crm.example.com/hooks/ertisauth/users/{{document._id}}",
"headers": {
"Authorization": "Bearer <crm_api_key>",
"X-Source": "ertisauth"
},
"body": {
"email": "{{document.email_address}}",
"name": "{{document.firstname}} {{document.lastname}}",
"source": "signup"
},
"uncoveredBody": false
},
"membership_id": "66f1c0d2a4b5c6d7e8f90123"
}| Field | Required | Description |
|---|---|---|
name | yes | Display name. |
description | no | |
event | yes | The event type that triggers the webhook, e.g. UserCreated. |
status | yes | active or passive. Passive webhooks are kept but not called. |
try_count | yes | How many times a failing request is tried, from 1 to 5. |
request.method | yes | The HTTP method: GET, POST, PUT, PATCH, DELETE… |
request.url | yes | The URL to call. May contain placeholders. |
request.headers | no | Headers to send. Values may contain placeholders. |
request.body | no | A JSON object. String values may contain placeholders. |
request.uncoveredBody | no | See The request body. false by default. |
Placeholders#
The URL, the header values and the string values of the body can contain placeholders in double braces, filled in from the event:
| Placeholder | Value |
|---|---|
{{event_type}} | The event type |
{{utilizer_id}} | Who caused the event |
{{event_time}} | When it happened |
{{membership_id}} | The membership id |
{{document.<field>}} | A field of the resource after the change, e.g. {{document.email_address}} |
{{prior.<field>}} | A field of the resource before the change |
Nested fields use dots: {{document.sys.created_by}}.
The values are escaped for where they are used, so that the data of an event (a user's name, for example) can't change the request:
- in the URL, values are URL-encoded (
../adminbecomes..%2Fadmin); - in headers, a value that would contain a line break is not sent;
- in the body, each placeholder stays inside its JSON string: it can't add fields or change the structure.
The request body#
By default (uncoveredBody: false) the request body wraps your body together with the event's documents:
{
"document": { "_id": "66f1c0d2a4b5c6d7e8f90127", "username": "ada", "email_address": "ada@example.com", "…": "…" },
"prior": null,
"payload": {
"email": "ada@example.com",
"name": "Ada Lovelace",
"source": "signup"
}
}With uncoveredBody: true the request body is your body alone, after the placeholders are filled in:
{
"email": "ada@example.com",
"name": "Ada Lovelace",
"source": "signup"
}Use uncoveredBody: true when the receiving API expects a specific format.
Delivery#
- Webhooks are called asynchronously, from a background queue: the request that caused the event doesn't wait for them, and a slow or failing receiver can't slow down or break ErtisAuth.
- A call counts as successful when the receiver answers with a 2xx status. Otherwise it is tried again, up to
try_counttimes in total, one right after the other. - Every attempt records a
WebhookRequestSentorWebhookRequestFailedevent with the request, the status code and the response body, so you can see what happened. - Webhooks are delivered at most once per attempt, not exactly once: make your receiver idempotent (for example by using
document._id), and expect that a call may be missed if ErtisAuth restarts while it is queued.
Securing your receiver#
- Use HTTPS.
- Authenticate the calls: put a secret in a header (
"Authorization": "Bearer <secret>") or in the URL, and check it in your receiver. - Don't trust the body blindly: when in doubt, read the resource from ErtisAuth with an application token.
Endpoints#
All routes are under /memberships/{membershipId}.
| Method | Route | Description | Permission |
|---|---|---|---|
GET | /webhooks/{id} | Get a webhook | webhooks.read.{id} |
GET | /webhooks | List webhooks | webhooks.read |
POST | /webhooks/_query | Query webhooks | webhooks.read |
POST | /webhooks | Create a webhook | webhooks.create |
PUT | /webhooks/{id} | Update a webhook | webhooks.update.{id} |
DELETE | /webhooks/{id} | Delete a webhook | webhooks.delete.{id} |
DELETE | /webhooks | Delete several webhooks | webhooks.delete |
Create a webhook#
curl -X POST https://auth.example.com/memberships/<membership_id>/webhooks \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json' \
-d '{
"name": "Notify on user deletion",
"event": "UserDeleted",
"status": "active",
"try_count": 3,
"request": {
"method": "DELETE",
"url": "https://shop.example.com/api/customers/{{prior._id}}",
"headers": { "Authorization": "Bearer <shop_api_key>" }
}
}'Response 201 Created: the webhook.
| Error | When |
|---|---|
400 ModelValidationError | A required field is missing, the event type or the HTTP method is unknown, or try_count is not between 1 and 5 |
404 MembershipNotFound | The membership does not exist |
409 WebhookAlreadyExists | The slug is taken |
Update and delete#
PUT /webhooks/{id} replaces the webhook with the body (same fields as create). An update without any change answers 409 IdenticalDocumentError. DELETE /webhooks/{id} answers 204 No Content.
Events#
WebhookCreated, WebhookUpdated, WebhookDeleted, and the delivery events WebhookRequestSent and WebhookRequestFailed. See Events.
Found a mistake in the docs? Open an issue