Skip to main content

Webhooks: Advanced Guide

A webhook subscription tells Full Fabric to send an HTTP POST to a URL you own every time a chosen event happens in your institution, for example an application being submitted or an offer being accepted. Your system receives the event a few seconds after

Webhooks are part of the Automation module. Each subscription is stored as an automation rule with a single action, "notify URL", so it behaves like any other automation: it can be suspended, resumed and deleted, and every delivery attempt is logged.

Key facts:

  • Direction: outbound only. Full Fabric calls your endpoint; it does not accept incoming webhooks through this feature.

  • Method and format: POST with a JSON body (Content-Type: application/json).

  • One event, one subscription: a subscription listens to one event type and posts to one URL. To send the same event to two systems, create two subscriptions.

  • Where: Automation > Webhooks (/automation/webhooks), with two tabs: Subscriptions and Activity.

Setting up a webhook

  1. Go to General settings > Webhooks and open the Subscriptions tab.

  2. Click Create subscription.

  3. In Event type, pick the event to listen to. The list is searchable by label (for example "Offer accepted") or by technical topic (for example automation.offers.accepts).

  4. For Profile transition only: optionally choose an Initial state and a Target state. Leave either on Any to match every state. Picking a state without a substate matches all its substates.

  5. In Webhook URL, enter the full address of your endpoint, starting with https:// (for example https://example.com/webhook).

  6. Click Create. The subscription appears in the list and starts receiving events immediately.

The subscription list shows, per row: the Trigger (event type, plus the states for profile transitions), the URL, Runs (how many times it has fired) and Created date. The menu at the end of each row lets you Suspend, Resume or Delete it.

Rules applied when you save

Check

Result if it fails

Event type and URL are both filled in

event_type is required / url is required

URL passes the outbound URL safety check (see Security)

url followed by the reason, and the subscription is not created

Event type is a supported event

Invalid event_type

No other subscription has the same event type, URL and states

Subscription already exists

To change the URL of an existing subscription, create a new one and delete the old one. Subscriptions cannot be edited in place.

Suspending versus deleting

  • Suspend stops deliveries but keeps the subscription and its activity history. Events that happen while it is suspended are not queued and are not sent later.

  • Resume starts deliveries again for new events.

  • Delete removes the subscription. System subscriptions created by Full Fabric cannot be deleted.

Event catalogue

Every event below is available in the Event type list. Each table gives the label shown in Full Fabric, the topic sent as event_type, exactly what causes it to fire, and the fields it carries on top of the common envelope (tenant, event_type, message_type, message_id, described in the payload reference). All ids are 24-character hexadecimal strings.

Applications

Event

Topic (event_type)

Fires when

Payload fields

Application started

automation.applics.starts

An application moves into the started state, whether the applicant starts it or staff create it

applic_id, profile_id (applicant), user_id (who did it), template_id, primary_choice_context

Application pending payment

automation.applics.pendingpayments

A started application moves to pending payment because the template requires a fee before submission

Same as Application started

Application pre-submitted

automation.applics.pre.submissions

Just before an application is submitted, while the submission is being processed

Same as Application started

Application submitted

automation.applics.submissions

An application has been submitted

Same as Application started

Application unsubmitted

automation.applics.unsubmissions

Staff revert a submitted application back to unsubmitted

applic_id, profile_id, user_id, template_id

Application updated

automation.applics.updates

Answers in an application are saved. Batched: at most one event per application every 20 seconds, carrying the latest state

applic_id, profile_id, user_id (may be empty), template_id, primary_choice_context

Application template closed

automation.applics.template.closed

An application template passes its closing date. Checked every minute, only for templates that have active automations

template_id, declared_closed_at

primary_choice_context describes the applicant's first choice: context_id and context_type (the class chosen), campus_id, and children (courses picked within that choice). It is null when no choice was made.

Offers

Event

Topic (event_type)

Fires when

Payload fields

Offer made

automation.offers.offers

An offer is sent to an applicant

offer_id, offer_template_id, profile_id (applicant), user_id (who did it), context_id (class), context_type

Offer accepted conditionally

automation.offers.conditionalaccepts

The applicant accepts an offer that still has conditions to meet

Same as Offer made

Offer submitted

automation.offers.submissions

The applicant submits the offer form

Same as Offer made

Offer accepted

automation.offers.accepts

The offer is accepted

Same as Offer made

Offer declined

automation.offers.declines

The offer is declined

Same as Offer made

Profiles and lifecycle

Event

Topic (event_type)

Fires when

Payload fields

Profile transition

automation.profiles.roles.changes or automation.profiles.roles.adds

A profile gets a new role in a class, or an existing role changes lifecycle state. Filtered by the Initial and Target states you chose. Withdrawals do not trigger it

profile_id, role_id, context_id (class), context_type, state, substate, state_and_substate, withdrawn, user_id (when a person made the change). On changes also previous_state_and_substate and previous_withdrawn

Profile role removed

automation.profiles.roles.removes

A profile's role in a class is removed

profile_id, role_id, context_id, context_type, previous_state_and_substate, previous_withdrawn, user_id

Profile withdrawn

automation.profiles.roles.withdrawn

Staff withdraw a profile from a class, individually or in bulk

profile_id, role_id, context_id, context_type, state, substate, state_and_substate, user_id

Profile activated

automation.profiles.activated

A profile becomes active: sign-up confirmed, first SSO login, or staff activation

profile_id

Profile deactivated

automation.profiles.deactivated

A profile is set to inactive

profile_id

Profile suspended

automation.profiles.suspended

A profile is suspended by staff, through the API, or because its access period expired at login

profile_id

States are written as state::substate, for example applicant::submitted. A Profile transition with an Initial state set only fires on changes, never when a role is first added, because a new role has no previous state.

Forms

Event

Topic (event_type)

Fires when

Payload fields

Form submission

automation.forms.submissions

Any form in the institution is submitted. A subscription created here is not limited to one form: filter on form_id in your endpoint

submission_id, form_id, profile_id, id, ip, created_at, updated_at, and one field per form question, keyed by the question's field name

Evaluations

Event

Topic (event_type)

Fires when

Payload fields

Evaluation assigned

automation.evaluations.assignments

An evaluation is created or assigned to an evaluator, including bulk assignment

evaluation_id, evaluation_template_id, assignee_id (evaluator), profile_id (person evaluated)

Evaluation submitted

automation.evaluations.submitted

An evaluator submits an evaluation

Same as Evaluation assigned

Events

Event

Topic (event_type)

Fires when

Payload fields

Event response

automation.events.responses.adds

Someone registers for an event for the first time. Changes to an existing response do not trigger it

response_id, event_id, profile_id, response_type (Events::Response::Yes, No or Maybe), response_submitted_at, attended, number_of_guests, and one field per registration form question

Event reminder

automation.events.reminders

A scheduled event reminder is sent. Fires once per attendee who answered Yes or Maybe

reminder_id, response_id, profile_id

Payments

Event

Topic (event_type)

Fires when

Payload fields

Payment plan published

automation.paymentplans.published

A payment plan is published to the student for the first time

payment_plan_id, payment_plan_template_id, profile_id

Payment plan paid

automation.paymentplans.paid

All fees of a payment plan are paid

Same as Payment plan published

Payment plan deleted

automation.paymentplans.deleted

A payment plan is deleted

Same as Payment plan published

First fee paid

automation.paymentplans.fees.paidfirst

The first fee of a payment plan becomes paid

fee_id, payment_plan_id, payment_plan_template_id, profile_id

Payment plan fee paid

automation.paymentplans.fees.paid

Any fee becomes paid, by a completed transaction or a discount covering it. Batched per fee over 5 seconds

Same as First fee paid

Payment plan fee deleted

automation.paymentplans.fees.deleted

A fee is deleted from a payment plan

Same as First fee paid

Academic affairs

Event

Topic (event_type)

Fires when

Payload fields

Registration

automation.registrations.registered

A student's course registration becomes registered, either created directly or confirmed from conditional

registration_id, profile_id, course_id, transcript_id, transcript_subject_id

Unregistration

affairs.registrations.deleted

A course registration is rejected or deleted, including when the profile is anonymised

registration_id, profile_id, course_id, retry_count

Class change

automation.classes.changes

A class's settings are edited and saved

class_of_id

Billing

Event

Topic (event_type)

Fires when

Payload fields

Billing contract created

automation.billing.contracts.created

A billing contract is created

contract_id, start_date, end_date

Billing contract updated

automation.billing.contracts.updated

A billing contract is changed

contract_id, start_date, end_date, dates_changed

Payload reference

Each delivery is one POST with header Content-Type: application/json and a flat JSON object. It holds the event's own fields (see the catalogue) plus four envelope fields that are always present.

Field

Type

Meaning

event_type

string

The topic that fired, for example automation.applics.submissions. Route on this field.

tenant

string

Your institution's handle, the subdomain of your Full Fabric site. Useful when one endpoint serves several institutions.

message_id

string (UUID)

Unique id of this event. Use it to detect duplicates.

message_type

string

Internal channel name. It can carry your handle as a prefix and dashes instead of dots (acme-automation-applics-submissions). Use event_type instead.

Value formats:

  • Ids are 24-character hexadecimal strings, for example 65f1c0e2a4b3d90012ab34cd. Use them with the Full Fabric API to fetch the full record.

  • Dates and times are ISO 8601 strings in UTC, for example 2026-10-15T09:42:17.123Z.

  • Lifecycle states are state::substate strings.

  • A field without a value is sent as null or left out.

Payloads carry ids, not full records. To get an applicant's name or an application's answers, call the API with the id you received. Form submissions and event responses are the exception: they include the answers given.

Example: Application submitted

{   "user_id": "65f1c0e2a4b3d90012ab34cd",   "profile_id": "65f1c0e2a4b3d90012ab34cd",   "applic_id": "6710a3b9c2e4f50013de5678",   "template_id": "66a2b1c0d9e8f70011aa9012",   "primary_choice_context": {     "context_id": "64b0a1b2c3d4e50010bc7890",     "context_type": "Institutions::ClassOf",     "campus_id": "64b0a1b2c3d4e50010bc1111",     "children": []   },   "tenant": "acme",   "message_type": "acme-automation-applics-submissions",   "message_id": "3f6d2a8e-9b41-4c55-8a0e-1d2c3b4a5f60",   "event_type": "automation.applics.submissions" }

Example: Profile transition

{   "profile_id": "65f1c0e2a4b3d90012ab34cd",   "role_id": "65f1c0e2a4b3d90012ab4400",   "context_type": "Institutions::ClassOf",   "context_id": "64b0a1b2c3d4e50010bc7890",   "previous_state_and_substate": "applicant::submitted",   "previous_withdrawn": false,   "state": "applicant",   "substate": "accepted",   "state_and_substate": "applicant::accepted",   "withdrawn": false,   "user_id": "6600aa11bb22cc33dd44ee55",   "tenant": "acme",   "message_type": "acme-automation-profiles-roles-changes",   "message_id": "a1b2c3d4-e5f6-4711-8899-aabbccddeeff",   "event_type": "automation.profiles.roles.changes" }

Treat any field you do not use as optional, and ignore unknown fields: new fields may be added without notice.

Delivery, retries and activity log

Events are delivered in the background, usually within 5 to 30 seconds of the action, and at most 3 attempts are made per event. Nothing waits for your endpoint: a slow or failing endpoint never blocks the user who triggered the event.

How a delivery happens

  1. The action happens in Full Fabric (for example an offer is accepted).

  2. Full Fabric matches the event against active subscriptions and queues one delivery per match.

  3. A background worker picks queued deliveries up every 5 seconds and posts them to your URL.

Application updated and Payment plan fee paid add a batching window (20 seconds and 5 seconds) before step 2.

Timeouts and retries

Setting

Value

Connection timeout

5 seconds

Response timeout

10 seconds

Attempts

3 in total (1 + 2 retries)

Wait before retry 1

5 seconds

Wait before retry 2

15 seconds

What is retried depends on your endpoint's answer:

Endpoint answer

Outcome

2xx

Success. Logged once.

3xx or 4xx

Treated as final. Logged once and not retried. Redirects are not followed.

5xx

Logged and retried.

Timeout, refused connection, DNS or TLS error

Logged and retried.

After the third failed attempt the event is dropped and marked as failed. Full Fabric does not replay it later, and the subscription stays active for the next events. There is no automatic suspension after repeated failures.

Delivery is at least once and not ordered. Two events about the same record can arrive in either order, and a retry after a timeout can deliver an event your endpoint already processed. Deduplicate on message_id and, when order matters, re-read the record from the API.

Activity log

The Activity tab (Automation > Webhooks > Activity) lists every delivery attempt across all subscriptions, newest first:

Column

Shows

Trigger

The event type and subscription that fired

URL

Where the request was sent

Status

Your endpoint's HTTP status code, empty when no response was received

Payload

The exact JSON body that was sent

Date

When the attempt was made

Each attempt has its own row, so a delivery that succeeded on the second try shows a failed row and a successful row. When no response came back (timeout, connection error), the row records the error message instead of a status. The log also stores your endpoint's response body, truncated to 10,000 characters.

Security

Requests are not signed and carry no authentication header, so your endpoint must treat every call as untrusted until checked. Use these measures together:

  • Use HTTPS. Plain http:// URLs are accepted but send personal data in clear text.

  • Use a hard-to-guess URL. Include a long random token in the path, for example https://example.com/hooks/ff/7Qm2…, and reject calls to any other path. Anyone who can see the subscription list or the activity log can see this URL, so give webhook access only to staff who need it.

  • Verify before acting. Use the ids in the payload to fetch the record from the Full Fabric API with your own credentials, and act on what the API returns rather than on the payload alone.

  • Deduplicate on message_id.

  • Minimise data. Payloads carry personal data (profile ids, and form or registration answers for those events). Store only what you need and apply your own retention rules.

When a subscription is saved, Full Fabric rejects URLs that are not http or https, have no host, or point at cloud metadata addresses (169.254.x.x, metadata.google.internal).

Permissions

Managing webhooks requires the Automation access feature on the staff member's role. The same feature gives access to the activity log, which shows the payloads sent.

Troubleshooting

Symptom

Likely cause and fix

No rows in the Activity tab

The event has not happened since the subscription was created, the subscription is suspended, or the action did not match its filters (for example the states of a Profile transition). Trigger the action yourself and wait 30 seconds.

Nothing is delivered in a sandbox

Webhook deliveries run only in live institutions. Sandboxes do not process automations, so test against your live site with a test record.

Rows show status 4xx

Your endpoint rejected the request. Check its path, authentication and body parsing. 4xx answers are not retried.

Rows show an error and no status

Full Fabric could not reach your endpoint: DNS, TLS certificate, firewall or a reply slower than 10 seconds. Answer 200 fast and do slow work afterwards.

The same event arrives twice

A retry after a timeout or a 5xx. Deduplicate on message_id.

A Profile transition with an Initial state never fires when a student is first added to a class

Expected: adding a role has no previous state. Leave Initial state on Any to catch additions.

Form submission fires for every form

Expected: the subscription covers all forms. Filter on form_id.

Changing an event registration sends nothing

Expected: Event response fires only for new registrations.

Subscription already exists

A subscription with the same event, URL and states exists. Use it, or vary the URL.

Did this answer your question?