diff --git a/manifests/integrations/eventhub-credentials-sync/_base/kustomization.yaml b/manifests/integrations/eventhub-credentials-sync/_base/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..dfd56766df09ef46f0c3911750ac2d6acfe95c75
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_base/kustomization.yaml
@@ -0,0 +1,27 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+commonLabels:
+  app: credentials-sync-eventhub
+
+resources:
+  - sync.yaml
+
+vars:
+  - name: KUBE_SECRET
+    objref:
+      kind: ConfigMap
+      name: credentials-sync-eventhub
+      apiVersion: v1
+    fieldref:
+      fieldpath: data.KUBE_SECRET
+  - name: ADDRESS
+    objref:
+      kind: ConfigMap
+      name: credentials-sync-eventhub
+      apiVersion: v1
+    fieldref:
+      fieldpath: data.ADDRESS
+
+configurations:
+  - kustomizeconfig.yaml
diff --git a/manifests/integrations/eventhub-credentials-sync/_base/kustomizeconfig.yaml b/manifests/integrations/eventhub-credentials-sync/_base/kustomizeconfig.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..61edffd433de240e0272dc879eee4d35853d9751
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_base/kustomizeconfig.yaml
@@ -0,0 +1,3 @@
+varReference:
+- path: rules/resourceNames
+  kind: Role
diff --git a/manifests/integrations/eventhub-credentials-sync/_base/sync.yaml b/manifests/integrations/eventhub-credentials-sync/_base/sync.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..62ea86f0d9205bdf131cabaf4597f3023c9d03de
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_base/sync.yaml
@@ -0,0 +1,133 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: credentials-sync-eventhub
+data:
+  # Patch this ConfigMap with additional values needed for your cloud
+  KUBE_SECRET: webhook-url # does not yet exist -- will be created in the same Namespace
+  ADDRESS: "fluxv2" # the Azure Event Hub name
+  SYNC_PERIOD: "3600" # tokens expire; refresh faster than that
+
+---
+# This Deployment frequently fetches registry tokens and applies them as an imagePullSecret.
+# It's done as a 1-replica Deployment rather than a CronJob, because CronJob scheduling can
+# block cluster bootstraps and cold-reboots from obtaining registry tokens for a considerable time.
+# This deployment will immediately fetch a token, which reduces latency for working image updates.
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+spec:
+  replicas: 1
+  strategy:
+    type: Recreate
+  template:
+    spec:
+      serviceAccountName: credentials-sync-eventhub
+      securityContext:
+        runAsNonRoot: true
+        runAsUser: 1001
+      containers:
+        - image: busybox # override this with a cloud-specific image
+          name: sync
+          envFrom:
+            - configMapRef:
+                name: credentials-sync-eventhub
+          env:
+            - name: RECONCILE_SH # override this env var with a shell function in a kustomize patch
+              value: |-
+                reconcile() {
+                  echo reconciling...
+                }
+          command:
+            - bash
+            - -ceu
+            - |-
+              # template reconcile() into the script
+              # env var is expanded by k8s before the pod starts
+              $(RECONCILE_SH)
+
+              apply-secret() {
+                /kbin/kubectl create secret generic "${1}" \
+                  --from-literal=token="${2}" \
+                  --from-literal=address="${3}" \
+                  --dry-run=client -o=yaml \
+                  | grep -v "creationTimestamp:" \
+                  | /kbin/kubectl apply -f -
+              }
+
+              pause_loop() {
+                sleep "${SYNC_PERIOD:-3600}" || true
+              }
+
+              graceful_exit() {
+                echo "Trapped signal -- $(date)"
+                job_ids="$(
+                  jobs \
+                    | grep "pause_loop" \
+                    | cut -d] -f1 \
+                    | tr [ %
+                  )"
+                # shellcheck disable=SC2086
+                if [ "${job_ids}" ]; then
+                  kill ${job_ids}
+                fi
+                wait
+                echo "Graceful exit -- $(date)"
+              }
+
+              trap graceful_exit INT TERM
+
+              echo "Loop started (period: ${SYNC_PERIOD} s) -- $(date)"
+              while true; do
+                reconcile & wait $!
+                pause_loop & wait $!
+              done
+          resources: {}
+          volumeMounts:
+            - mountPath: /.azure
+              name: cache-volume
+      volumes:
+        - emptyDir: {}
+          name: cache-volume
+
+# RBAC necessary for our Deployment to apply our secret that will store the JWT token
+---
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+rules:
+  - apiGroups: [""]
+    resources:
+      - secrets
+    verbs:
+      - get
+      - create
+      - update
+      - patch
+    # # Lock this down to the specific Secret name  (Optional)
+    #resourceNames:
+    #  - $(KUBE_SECRET) # templated from kustomize vars referencing ConfigMap, also see kustomizeconfig.yaml
+---
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+subjects:
+  - kind: ServiceAccount
+    name: credentials-sync-eventhub
+roleRef:
+  kind: Role
+  name: credentials-sync-eventhub
+  apiGroup: rbac.authorization.k8s.io
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/_base/kustomization.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/_base/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..dfd56766df09ef46f0c3911750ac2d6acfe95c75
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/_base/kustomization.yaml
@@ -0,0 +1,27 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+commonLabels:
+  app: credentials-sync-eventhub
+
+resources:
+  - sync.yaml
+
+vars:
+  - name: KUBE_SECRET
+    objref:
+      kind: ConfigMap
+      name: credentials-sync-eventhub
+      apiVersion: v1
+    fieldref:
+      fieldpath: data.KUBE_SECRET
+  - name: ADDRESS
+    objref:
+      kind: ConfigMap
+      name: credentials-sync-eventhub
+      apiVersion: v1
+    fieldref:
+      fieldpath: data.ADDRESS
+
+configurations:
+  - kustomizeconfig.yaml
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/_base/kustomizeconfig.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/_base/kustomizeconfig.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..61edffd433de240e0272dc879eee4d35853d9751
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/_base/kustomizeconfig.yaml
@@ -0,0 +1,3 @@
+varReference:
+- path: rules/resourceNames
+  kind: Role
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/_base/sync.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/_base/sync.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e7fd16a71a9883fad20d1ae98fb90ff1d3d321d5
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/_base/sync.yaml
@@ -0,0 +1,109 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: credentials-sync-eventhub
+data:
+  # Patch this ConfigMap with additional values needed for your cloud
+  KUBE_SECRET: webhook-url # does not yet exist -- will be created in the same Namespace
+  ADDRESS: "fluxv2" # the Azure Event Hub name
+
+---
+# This CronJob frequently fetches registry tokens and applies them as an imagePullSecret.
+# note: CronJob scheduling can block cluster bootstraps and cold-reboots from obtaining registry tokens for a considerable time.
+# To run the job immediately, do `kubectl create job --from=cronjob/credentials-sync-eventhub -n flux-system credentials-sync-eventhub-init`
+apiVersion: batch/v1beta1
+kind: CronJob
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+spec:
+  suspend: false
+  schedule: 0 */6 * * *
+  failedJobsHistoryLimit: 1
+  successfulJobsHistoryLimit: 1
+  jobTemplate:
+    spec:
+      template:
+        spec:
+          serviceAccountName: credentials-sync-eventhub
+          securityContext:
+            runAsNonRoot: true
+            runAsUser: 1001
+          restartPolicy: Never
+          containers:
+            - image: busybox # override this with a cloud-specific image
+              name: sync
+              envFrom:
+                - configMapRef:
+                    name: credentials-sync-eventhub
+              env:
+                - name: RECONCILE_SH # override this env var with a shell function in a kustomize patch
+                  value: |-
+                    reconcile() {
+                      echo reconciling...
+                    }
+              command:
+                - bash
+                - -ceu
+                - |-
+                  # template reconcile() into the script
+                  # env var is expanded by k8s before the pod starts
+                  $(RECONCILE_SH)
+
+                  apply-secret() {
+                    /kbin/kubectl create secret generic "${1}" \
+                      --from-literal=token="${2}" \
+                      --from-literal=address="${3}" \
+                      --dry-run=client -o=yaml \
+                      | grep -v "creationTimestamp:" \
+                      | /kbin/kubectl apply -f -
+                  }
+
+                  reconcile
+              resources: {}
+              volumeMounts:
+                - mountPath: /.azure
+                  name: cache-volume
+          volumes:
+            - emptyDir: {}
+              name: cache-volume
+
+# RBAC necessary for our Deployment to apply our secret that will store the JWT token
+---
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+rules:
+  - apiGroups: [""]
+    resources:
+      - secrets
+    verbs:
+      - get
+      - create
+      - update
+      - patch
+    # # Lock this down to the specific Secret name  (Optional)
+    #resourceNames:
+    #  - $(KUBE_SECRET) # templated from kustomize vars referencing ConfigMap, also see kustomizeconfig.yaml
+---
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+subjects:
+  - kind: ServiceAccount
+    name: credentials-sync-eventhub
+roleRef:
+  kind: Role
+  name: credentials-sync-eventhub
+  apiGroup: rbac.authorization.k8s.io
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/az-identity.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/az-identity.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1591126b0bdf40e58d5b4b1e8ce04ffa82352b20
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/az-identity.yaml
@@ -0,0 +1,16 @@
+# This is a stub resource patched by config-patches.yaml, so that all config is visible in one file
+---
+apiVersion: aadpodidentity.k8s.io/v1
+kind: AzureIdentity
+metadata:
+  name: lab # if this is changed, also change in config-patches.yaml
+  namespace: flux-system
+---
+apiVersion: aadpodidentity.k8s.io/v1
+kind: AzureIdentityBinding
+metadata:
+  name: lab
+  namespace: flux-system
+spec:
+  azureIdentity: lab
+  selector: lab
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/config-patches.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/config-patches.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3d0ffac40991ba338a3abba5ad13c55f2c0925d2
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/config-patches.yaml
@@ -0,0 +1,50 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: credentials-sync-eventhub
+data:
+  KUBE_SECRET: webhook-url # does not yet exist -- will be created in the same Namespace
+  ADDRESS: "fluxv2" # the Azure Event Hub name
+
+# Create an identity in Azure and assign it a role to write to Azure Event Hub (note: the identity's resourceGroup should match the Azure Event Hub):
+#     az identity create -n eventhub-write
+#     az role assignment create --role eventhub --assignee-object-id "$(az identity show -n eventhub-write -o tsv --query principalId)"
+# Fetch the clientID and resourceID to configure the AzureIdentity spec below:
+#     az identity show -n eventhub-write -otsv --query clientId
+#     az identity show -n eventhub-write -otsv --query resourceId
+---
+apiVersion: aadpodidentity.k8s.io/v1
+kind: AzureIdentity
+metadata:
+  name: lab
+  namespace: flux-system
+spec:
+  clientID: 82d01fb0-7799-4d9d-92c7-21e7632c0000
+  resourceID: /subscriptions/82d01fb0-7799-4d9d-92c7-21e7632c0000/resourceGroups/stealthybox/providers/Microsoft.ManagedIdentity/userAssignedIdentities/eventhub-write
+  type: 0
+---
+apiVersion: aadpodidentity.k8s.io/v1
+kind: AzureIdentityBinding
+metadata:
+  name: lab
+  namespace: flux-system
+spec:
+  azureIdentity: jwt-lab
+  selector: jwt-lab
+
+# Set the reconcile period + specify the pod-identity via the aadpodidbinding label
+---
+apiVersion: batch/v1beta1
+kind: CronJob
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+spec:
+  schedule: 0 * * * * # JWT tokens expire every 24 hours; refresh faster than that
+  jobTemplate:
+    spec:
+      template:
+        metadata:
+          labels:
+            aadpodidbinding: $(AZ_IDENTITY_NAME) # match the AzureIdentity name
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/kubectl-patch.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/kubectl-patch.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d05c07e597668d558ebf01a0760c48f0542ac70a
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/kubectl-patch.yaml
@@ -0,0 +1,34 @@
+apiVersion: batch/v1beta1
+kind: CronJob
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+spec:
+  jobTemplate:
+    spec:
+      template:
+        spec:
+          initContainers:
+            - image: bitnami/kubectl
+              securityContext:
+                privileged: false
+                readOnlyRootFilesystem: true
+                allowPrivilegeEscalation: false
+              name: copy-kubectl
+              # it's okay to do this because kubectl is a statically linked binary
+              command:
+                - sh
+                - -ceu
+                - cp $(which kubectl) /kbin/
+              resources: {}
+              volumeMounts:
+                - name: kbin
+                  mountPath: /kbin
+          containers:
+            - name: sync
+              volumeMounts:
+                - name: kbin
+                  mountPath: /kbin
+          volumes:
+            - name: kbin
+              emptyDir: {}
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/kustomization.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..14a0d59ff3538013b9bc4a35d1b7f71120ad3869
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/kustomization.yaml
@@ -0,0 +1,28 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+namePrefix: jwt-
+commonLabels:
+  app: jwt-eventhub-credentials-sync
+
+namespace: flux-system
+
+bases:
+  - ../_base
+resources:
+  - az-identity.yaml
+
+patchesStrategicMerge:
+  - config-patches.yaml
+  - kubectl-patch.yaml
+  - reconcile-patch.yaml
+
+vars:
+  - name: AZ_IDENTITY_NAME
+    objref:
+      kind: AzureIdentity
+      name: lab
+      apiVersion: aadpodidentity.k8s.io/v1
+
+configurations:
+  - kustomizeconfig.yaml
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/kustomizeconfig.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/kustomizeconfig.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..175f04a29544d00c2c0000493fb7cea43434a163
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/kustomizeconfig.yaml
@@ -0,0 +1,3 @@
+varReference:
+  - path: spec/jobTemplate/spec/template/metadata/labels
+    kind: CronJob
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/reconcile-patch.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/reconcile-patch.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1e96e5366df52cb6f64829e0778e71503c50ceb4
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/azure/reconcile-patch.yaml
@@ -0,0 +1,27 @@
+apiVersion: batch/v1beta1
+kind: CronJob
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+spec:
+  jobTemplate:
+    spec:
+      template:
+        spec:
+          containers:
+            - name: sync
+              image: mcr.microsoft.com/azure-cli
+              env:
+                - name: RECONCILE_SH
+                  value: |-
+                    reconcile() {
+                      echo "Starting JWT token sync -- $(date)"
+                      echo "Logging into Azure"
+                      az login --identity
+                      echo "Getting JWT token"
+                      token=$(az account get-access-token --resource https://eventhubs.azure.net |jq -r .accessToken)
+                      echo "Creating secret: ${KUBE_SECRET}"
+                      apply-secret "${KUBE_SECRET}" ${token} "${ADDRESS}"
+                      echo "Finished JWT token sync -- $(date)"
+                      echo
+                    }
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/config-patches.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/config-patches.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b2374ac04c6cd91af63d4502b39d16187d3d009e
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/config-patches.yaml
@@ -0,0 +1,16 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+data:
+  KUBE_SECRET: webhook-url # does not yet exist -- will be created in the same Namespace
+  ADDRESS: "fluxv2" # the Azure Event Hub name
+
+# Create an identity in Azure and assign it a role to write to Azure Event Hub (note: the identity's resourceGroup should match the Azure Event Hub):
+#     az identity create -n eventhub-write
+#     az role assignment create --role eventhub --assignee-object-id "$(az identity show -n eventhub-write -o tsv --query principalId)"
+# Fetch the clientID and resourceID to configure the AzureIdentity spec below:
+#     az identity show -n eventhub-write -otsv --query clientId
+#     az identity show -n eventhub-write -otsv --query resourceId
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/kubectl-patch.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/kubectl-patch.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d05c07e597668d558ebf01a0760c48f0542ac70a
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/kubectl-patch.yaml
@@ -0,0 +1,34 @@
+apiVersion: batch/v1beta1
+kind: CronJob
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+spec:
+  jobTemplate:
+    spec:
+      template:
+        spec:
+          initContainers:
+            - image: bitnami/kubectl
+              securityContext:
+                privileged: false
+                readOnlyRootFilesystem: true
+                allowPrivilegeEscalation: false
+              name: copy-kubectl
+              # it's okay to do this because kubectl is a statically linked binary
+              command:
+                - sh
+                - -ceu
+                - cp $(which kubectl) /kbin/
+              resources: {}
+              volumeMounts:
+                - name: kbin
+                  mountPath: /kbin
+          containers:
+            - name: sync
+              volumeMounts:
+                - name: kbin
+                  mountPath: /kbin
+          volumes:
+            - name: kbin
+              emptyDir: {}
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/kustomization.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..109f3a07d53f28e4f5443dbdfb10070171da749b
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/kustomization.yaml
@@ -0,0 +1,21 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+namePrefix: jwt-
+commonLabels:
+  app: jwt-eventhub-credentials-sync
+
+namespace: flux-system
+
+bases:
+  - ../_base
+resources:
+  - secret-azure-credentials.yaml
+
+patchesStrategicMerge:
+  - config-patches.yaml
+  - kubectl-patch.yaml
+  - reconcile-patch.yaml
+
+configurations:
+  - kustomizeconfig.yaml
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/kustomizeconfig.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/kustomizeconfig.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..175f04a29544d00c2c0000493fb7cea43434a163
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/kustomizeconfig.yaml
@@ -0,0 +1,3 @@
+varReference:
+  - path: spec/jobTemplate/spec/template/metadata/labels
+    kind: CronJob
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/reconcile-patch.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/reconcile-patch.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..67eebfc9d6482241711d898bf69b4afe3f54f811
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/reconcile-patch.yaml
@@ -0,0 +1,42 @@
+apiVersion: batch/v1beta1
+kind: CronJob
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+spec:
+  jobTemplate:
+    spec:
+      template:
+        spec:
+          containers:
+            - name: sync
+              image: mcr.microsoft.com/azure-cli
+              env:
+                - name: RECONCILE_SH
+                  value: |-
+                    reconcile() {
+                      echo "Starting JWT token sync -- $(date)"
+                      echo "Logging into Azure"
+                      az login --service-principal -u ${AZURE_CLIENT_ID} -p ${AZURE_CLIENT_SECRET} --tenant ${AZURE_TENANT_ID}
+                      echo "Getting JWT token"
+                      token=$(az account get-access-token --resource https://eventhubs.azure.net |jq -r .accessToken)
+                      echo "Creating secret: ${KUBE_SECRET}"
+                      apply-secret "${KUBE_SECRET}" ${token} "${ADDRESS}"
+                      echo "Finished JWT token sync -- $(date)"
+                      echo
+                    }
+                - name: AZURE_CLIENT_ID
+                  valueFrom:
+                    secretKeyRef:
+                      name: azure-credentials
+                      key: AZURE_CLIENT_ID
+                - name: AZURE_CLIENT_SECRET
+                  valueFrom:
+                    secretKeyRef:
+                      name: azure-credentials
+                      key: AZURE_CLIENT_SECRET
+                - name: AZURE_TENANT_ID
+                  valueFrom:
+                    secretKeyRef:
+                      name: azure-credentials
+                      key: AZURE_TENANT_ID
diff --git a/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/secret-azure-credentials.yaml b/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/secret-azure-credentials.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8a6d8a2c5582bfd204001307c0feef53d0a8010d
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/_cronjobs/generic/secret-azure-credentials.yaml
@@ -0,0 +1,14 @@
+apiVersion: v1
+data:
+  AZURE_CLIENT_ID: MDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMA==
+  AZURE_CLIENT_SECRET: c28tbXVjaC1zZWNyZXQ=
+  AZURE_TENANT_ID: MDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMA==
+kind: Secret
+metadata:
+  name: azure-credentials
+  namespace: flux-system
+type: Opaque
+# This is just a example secret, you should never store secrets in git.
+# One way forward can be to use sealed-secrets or SOPS
+# https://fluxcd.io/docs/guides/sealed-secrets/
+# https://fluxcd.io/docs/guides/mozilla-sops/
diff --git a/manifests/integrations/eventhub-credentials-sync/azure/az-identity.yaml b/manifests/integrations/eventhub-credentials-sync/azure/az-identity.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1591126b0bdf40e58d5b4b1e8ce04ffa82352b20
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/azure/az-identity.yaml
@@ -0,0 +1,16 @@
+# This is a stub resource patched by config-patches.yaml, so that all config is visible in one file
+---
+apiVersion: aadpodidentity.k8s.io/v1
+kind: AzureIdentity
+metadata:
+  name: lab # if this is changed, also change in config-patches.yaml
+  namespace: flux-system
+---
+apiVersion: aadpodidentity.k8s.io/v1
+kind: AzureIdentityBinding
+metadata:
+  name: lab
+  namespace: flux-system
+spec:
+  azureIdentity: lab
+  selector: lab
diff --git a/manifests/integrations/eventhub-credentials-sync/azure/config-patches.yaml b/manifests/integrations/eventhub-credentials-sync/azure/config-patches.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c285ed2c426234657943754ed84436f6b8d49e1e
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/azure/config-patches.yaml
@@ -0,0 +1,48 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: credentials-sync-eventhub
+data:
+  KUBE_SECRET: webhook-url # does not yet exist -- will be created in the same Namespace
+  ADDRESS: "fluxv2" # the Azure Event Hub name
+  SYNC_PERIOD: "3600" # tokens expire; refresh faster than that
+
+# Create an identity in Azure and assign it a role to write to Azure Event Hub (note: the identity's resourceGroup should match the Azure Event Hub):
+#     az identity create -n eventhub-write
+#     az role assignment create --role eventhub --assignee-object-id "$(az identity show -n eventhub-write -o tsv --query principalId)"
+# Fetch the clientID and resourceID to configure the AzureIdentity spec below:
+#     az identity show -n eventhub-write -otsv --query clientId
+#     az identity show -n eventhub-write -otsv --query resourceId
+---
+apiVersion: aadpodidentity.k8s.io/v1
+kind: AzureIdentity
+metadata:
+  name: lab
+  namespace: flux-system
+spec:
+  clientID: 82d01fb0-7799-4d9d-92c7-21e7632c0000
+  resourceID: /subscriptions/82d01fb0-7799-4d9d-92c7-21e7632c0000/resourceGroups/stealthybox/providers/Microsoft.ManagedIdentity/userAssignedIdentities/eventhub-write
+  type: 0
+---
+apiVersion: aadpodidentity.k8s.io/v1
+kind: AzureIdentityBinding
+metadata:
+  name: lab
+  namespace: flux-system
+spec:
+  azureIdentity: jwt-lab
+  selector: jwt-lab
+
+# Specify the pod-identity via the aadpodidbinding label
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+spec:
+  template:
+    metadata:
+      labels:
+        aadpodidbinding: $(AZ_IDENTITY_NAME) # match the AzureIdentity name
diff --git a/manifests/integrations/eventhub-credentials-sync/azure/kubectl-patch.yaml b/manifests/integrations/eventhub-credentials-sync/azure/kubectl-patch.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..65226a0f79f79b2e76c00e006096381c8cdcc896
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/azure/kubectl-patch.yaml
@@ -0,0 +1,32 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+spec:
+  template:
+    spec:
+      initContainers:
+        - image: bitnami/kubectl
+          securityContext:
+            privileged: false
+            readOnlyRootFilesystem: true
+            allowPrivilegeEscalation: false
+          name: copy-kubectl
+          # it's okay to do this because kubectl is a statically linked binary
+          command:
+            - sh
+            - -ceu
+            - cp $(which kubectl) /kbin/
+          resources: {}
+          volumeMounts:
+            - name: kbin
+              mountPath: /kbin
+      containers:
+        - name: sync
+          volumeMounts:
+            - name: kbin
+              mountPath: /kbin
+      volumes:
+        - name: kbin
+          emptyDir: {}
diff --git a/manifests/integrations/eventhub-credentials-sync/azure/kustomization.yaml b/manifests/integrations/eventhub-credentials-sync/azure/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..14a0d59ff3538013b9bc4a35d1b7f71120ad3869
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/azure/kustomization.yaml
@@ -0,0 +1,28 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+namePrefix: jwt-
+commonLabels:
+  app: jwt-eventhub-credentials-sync
+
+namespace: flux-system
+
+bases:
+  - ../_base
+resources:
+  - az-identity.yaml
+
+patchesStrategicMerge:
+  - config-patches.yaml
+  - kubectl-patch.yaml
+  - reconcile-patch.yaml
+
+vars:
+  - name: AZ_IDENTITY_NAME
+    objref:
+      kind: AzureIdentity
+      name: lab
+      apiVersion: aadpodidentity.k8s.io/v1
+
+configurations:
+  - kustomizeconfig.yaml
diff --git a/manifests/integrations/eventhub-credentials-sync/azure/kustomizeconfig.yaml b/manifests/integrations/eventhub-credentials-sync/azure/kustomizeconfig.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..afd68fe5de0524199549ab8c094377089ffbec79
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/azure/kustomizeconfig.yaml
@@ -0,0 +1,3 @@
+varReference:
+- path: spec/template/metadata/labels
+  kind: Deployment
diff --git a/manifests/integrations/eventhub-credentials-sync/azure/reconcile-patch.yaml b/manifests/integrations/eventhub-credentials-sync/azure/reconcile-patch.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..88d9086a38e95f931bee8cee3da6e0ae54f6d514
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/azure/reconcile-patch.yaml
@@ -0,0 +1,26 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+spec:
+  template:
+    spec:
+      containers:
+        - name: sync
+          image: mcr.microsoft.com/azure-cli
+          env:
+            - name: RECONCILE_SH
+              value: |-
+                reconcile() {
+                  echo "Starting JWT token sync -- $(date)"
+                  echo "Logging into Azure"
+                  az login --identity
+                  echo "Getting JWT token"
+                  token=$(az account get-access-token --resource https://eventhubs.azure.net |jq -r .accessToken)
+                  echo "Creating secret: ${KUBE_SECRET}"
+                  apply-secret "${KUBE_SECRET}" ${token} "${ADDRESS}"
+                  echo "Finished JWT token sync -- $(date)"
+                  echo
+                }
diff --git a/manifests/integrations/eventhub-credentials-sync/generic/config-patches.yaml b/manifests/integrations/eventhub-credentials-sync/generic/config-patches.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..9c1ca7944fb63d35da91f01b8691ec89d20df986
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/generic/config-patches.yaml
@@ -0,0 +1,17 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: credentials-sync-eventhub
+data:
+  KUBE_SECRET: webhook-url # does not yet exist -- will be created in the same Namespace
+  ADDRESS: "fluxv2" # the Azure Event Hub name
+  SYNC_PERIOD: "3600" # tokens expire; refresh faster than that
+
+# Create an identity in Azure and assign it a role to write to Azure Event Hub (note: the identity's resourceGroup should match the Azure Event Hub):
+#     az identity create -n eventhub-write
+#     az role assignment create --role eventhub --assignee-object-id "$(az identity show -n eventhub-write -o tsv --query principalId)"
+# Fetch the clientID and resourceID to configure the AzureIdentity spec below:
+#     az identity show -n eventhub-write -otsv --query clientId
+#     az identity show -n eventhub-write -otsv --query resourceId
+# Specify the pod-identity via the aadpodidbinding label
diff --git a/manifests/integrations/eventhub-credentials-sync/generic/kubectl-patch.yaml b/manifests/integrations/eventhub-credentials-sync/generic/kubectl-patch.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..65226a0f79f79b2e76c00e006096381c8cdcc896
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/generic/kubectl-patch.yaml
@@ -0,0 +1,32 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+spec:
+  template:
+    spec:
+      initContainers:
+        - image: bitnami/kubectl
+          securityContext:
+            privileged: false
+            readOnlyRootFilesystem: true
+            allowPrivilegeEscalation: false
+          name: copy-kubectl
+          # it's okay to do this because kubectl is a statically linked binary
+          command:
+            - sh
+            - -ceu
+            - cp $(which kubectl) /kbin/
+          resources: {}
+          volumeMounts:
+            - name: kbin
+              mountPath: /kbin
+      containers:
+        - name: sync
+          volumeMounts:
+            - name: kbin
+              mountPath: /kbin
+      volumes:
+        - name: kbin
+          emptyDir: {}
diff --git a/manifests/integrations/eventhub-credentials-sync/generic/kustomization.yaml b/manifests/integrations/eventhub-credentials-sync/generic/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..109f3a07d53f28e4f5443dbdfb10070171da749b
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/generic/kustomization.yaml
@@ -0,0 +1,21 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+namePrefix: jwt-
+commonLabels:
+  app: jwt-eventhub-credentials-sync
+
+namespace: flux-system
+
+bases:
+  - ../_base
+resources:
+  - secret-azure-credentials.yaml
+
+patchesStrategicMerge:
+  - config-patches.yaml
+  - kubectl-patch.yaml
+  - reconcile-patch.yaml
+
+configurations:
+  - kustomizeconfig.yaml
diff --git a/manifests/integrations/eventhub-credentials-sync/generic/kustomizeconfig.yaml b/manifests/integrations/eventhub-credentials-sync/generic/kustomizeconfig.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..afd68fe5de0524199549ab8c094377089ffbec79
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/generic/kustomizeconfig.yaml
@@ -0,0 +1,3 @@
+varReference:
+- path: spec/template/metadata/labels
+  kind: Deployment
diff --git a/manifests/integrations/eventhub-credentials-sync/generic/reconcile-patch.yaml b/manifests/integrations/eventhub-credentials-sync/generic/reconcile-patch.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..899534444223728fdcdef0c7a77be64402fdbe7c
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/generic/reconcile-patch.yaml
@@ -0,0 +1,41 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: credentials-sync-eventhub
+  namespace: flux-system
+spec:
+  template:
+    spec:
+      containers:
+        - name: sync
+          image: mcr.microsoft.com/azure-cli
+          env:
+            - name: RECONCILE_SH
+              value: |-
+                reconcile() {
+                  echo "Starting JWT token sync -- $(date)"
+                  echo "Logging into Azure"
+                  az login --service-principal -u ${AZURE_CLIENT_ID} -p ${AZURE_CLIENT_SECRET} --tenant ${AZURE_TENANT_ID}
+                  echo "Getting JWT token"
+                  token=$(az account get-access-token --resource https://eventhubs.azure.net |jq -r .accessToken)
+                  echo "Creating secret: ${KUBE_SECRET}"
+                  apply-secret "${KUBE_SECRET}" ${token} "${ADDRESS}"
+                  echo "Finished JWT token sync -- $(date)"
+                  echo
+                }
+            - name: AZURE_CLIENT_ID
+              valueFrom:
+                secretKeyRef:
+                  name: azure-credentials
+                  key: AZURE_CLIENT_ID
+            - name: AZURE_CLIENT_SECRET
+              valueFrom:
+                secretKeyRef:
+                  name: azure-credentials
+                  key: AZURE_CLIENT_SECRET
+            - name: AZURE_TENANT_ID
+              valueFrom:
+                secretKeyRef:
+                  name: azure-credentials
+                  key: AZURE_TENANT_ID
diff --git a/manifests/integrations/eventhub-credentials-sync/generic/secret-azure-credentials.yaml b/manifests/integrations/eventhub-credentials-sync/generic/secret-azure-credentials.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8a6d8a2c5582bfd204001307c0feef53d0a8010d
--- /dev/null
+++ b/manifests/integrations/eventhub-credentials-sync/generic/secret-azure-credentials.yaml
@@ -0,0 +1,14 @@
+apiVersion: v1
+data:
+  AZURE_CLIENT_ID: MDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMA==
+  AZURE_CLIENT_SECRET: c28tbXVjaC1zZWNyZXQ=
+  AZURE_TENANT_ID: MDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMA==
+kind: Secret
+metadata:
+  name: azure-credentials
+  namespace: flux-system
+type: Opaque
+# This is just a example secret, you should never store secrets in git.
+# One way forward can be to use sealed-secrets or SOPS
+# https://fluxcd.io/docs/guides/sealed-secrets/
+# https://fluxcd.io/docs/guides/mozilla-sops/