diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragment.kt
index 45d0b05c577a8851b7c938e2c7de45edc36cc89b..42200d5b710b1ddab8e1923a2b5b16f45693aa37 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragment.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragment.kt
@@ -70,6 +70,7 @@ class DebugOptionsFragment : Fragment(R.layout.fragment_test_debugoptions), Auto
                 environmentCdnurlSubmission.text = "Submission CDN:\n${state.urlSubmission}"
                 environmentCdnurlVerification.text = "Verification CDN:\n${state.urlVerification}"
                 environmentUrlDatadonation.text = "DataDonation:\n${state.urlDataDonation}"
+                environmentUrlQrcodePosterTemplate.text = "QR-Code Poster Template:\n${state.urlQrCodePosterTemplate}"
             }
         }
         vm.environmentChangeEvent.observe2(this) {
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/EnvironmentState.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/EnvironmentState.kt
index fb3a8ded80765be7fe43f2487775ff3bfcfa9b2a..3d72f93a074ce0bdeffa9323f695944f54aac039 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/EnvironmentState.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/EnvironmentState.kt
@@ -8,7 +8,9 @@ data class EnvironmentState(
     val urlSubmission: String,
     val urlDownload: String,
     val urlVerification: String,
-    val urlDataDonation: String
+    val urlDataDonation: String,
+    val urlQrCodePosterTemplate: String
+
 ) {
     companion object {
         internal fun EnvironmentSetup.toEnvironmentState() = EnvironmentState(
@@ -17,7 +19,8 @@ data class EnvironmentState(
             urlSubmission = submissionCdnUrl,
             urlDownload = downloadCdnUrl,
             urlVerification = verificationCdnUrl,
-            urlDataDonation = dataDonationCdnUrl
+            urlDataDonation = dataDonationCdnUrl,
+            urlQrCodePosterTemplate = qrCodePosterTemplateCdnUrl
         )
     }
 }
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestFragment.kt
index abd5b1c1be9461567ee0dbf1fccc308797c2bb08..0124e0857c06ebe0e76039d8c3cd7bcdefb838f8 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestFragment.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestFragment.kt
@@ -80,5 +80,13 @@ class QrCodeCreationTestFragment : Fragment(R.layout.fragment_test_qrcode_creati
         binding.generateQrCode.setOnClickListener {
             viewModel.createQrCode(binding.qrCodeText.text.toString())
         }
+
+        binding.downloadQrCodePosterTemplate.setOnClickListener {
+            viewModel.downloadQrCodePosterTemplate()
+        }
+
+        viewModel.qrCodePosterTemplate.observe2(this) { vectorDrawableBytes ->
+            binding.downloadedQrCodePoster.text = vectorDrawableBytes.utf8()
+        }
     }
 }
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestViewModel.kt
index a1eca9f8b1fbed87ee6f90a0a55725edd05ca75b..336f7fc486d6159c2e1e61d17920851a7e55a3aa 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestViewModel.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/qrcode/QrCodeCreationTestViewModel.kt
@@ -6,7 +6,7 @@ import android.graphics.pdf.PdfDocument
 import android.view.View
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import de.rki.coronawarnapp.appconfig.AppConfigProvider
+import de.rki.coronawarnapp.eventregistration.events.server.qrcodepostertemplate.QrCodePosterTemplateServer
 import de.rki.coronawarnapp.ui.eventregistration.organizer.details.QrCodeGenerator
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import de.rki.coronawarnapp.util.di.AppContext
@@ -14,6 +14,8 @@ import de.rki.coronawarnapp.util.files.FileSharing
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import okio.ByteString
+import okio.ByteString.Companion.toByteString
 import timber.log.Timber
 import java.io.File
 import java.io.FileOutputStream
@@ -23,12 +25,13 @@ class QrCodeCreationTestViewModel @AssistedInject constructor(
     private val fileSharing: FileSharing,
     private val qrCodeGenerator: QrCodeGenerator,
     @AppContext private val context: Context,
-    private val appConfigProvider: AppConfigProvider,
+    private val posterTemplateServer: QrCodePosterTemplateServer
 ) : CWAViewModel(dispatcher) {
 
     val qrCodeBitmap = SingleLiveEvent<Bitmap>()
     val errorMessage = SingleLiveEvent<String>()
     val sharingIntent = SingleLiveEvent<FileSharing.FileIntentProvider>()
+    val qrCodePosterTemplate = SingleLiveEvent<ByteString>()
 
     /**
      * Creates a QR Code [Bitmap] ,result is delivered by [qrCodeBitmap]
@@ -85,6 +88,17 @@ class QrCodeCreationTestViewModel @AssistedInject constructor(
         return File(dir, "CoronaWarnApp-Event.pdf")
     }
 
+    fun downloadQrCodePosterTemplate() {
+        launch {
+            try {
+                val posterTemplate = posterTemplateServer.downloadQrCodePosterTemplate()
+                qrCodePosterTemplate.postValue(posterTemplate.template.toByteArray().toByteString())
+            } catch (exception: Exception) {
+                errorMessage.postValue("Downloading Poster Template failed: ${exception.message}")
+            }
+        }
+    }
+
     @AssistedFactory
     interface Factory : SimpleCWAViewModelFactory<QrCodeCreationTestViewModel>
 }
diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_debugoptions.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_debugoptions.xml
index 5a3c1da5e950e6675de4ca1f8964c91f3c9deff7..224249114efbd69384ab29ac449aa49cae7f585c 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_debugoptions.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_debugoptions.xml
@@ -115,6 +115,17 @@
                     app:layout_constraintTop_toBottomOf="@+id/environment_cdnurl_verification"
                     tools:text="DataDonation: ?" />
 
+                <TextView
+                    android:id="@+id/environment_url_qrcode_poster_template"
+                    style="@style/body2"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_tiny"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/environment_url_datadonation"
+                    tools:text="QR-Code poster template: ?" />
+
                 <RadioGroup
                     android:id="@+id/environment_toggle_group"
                     android:layout_width="match_parent"
@@ -124,7 +135,7 @@
                     app:layout_constraintBottom_toBottomOf="parent"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
-                    app:layout_constraintTop_toBottomOf="@+id/environment_url_datadonation" />
+                    app:layout_constraintTop_toBottomOf="@+id/environment_url_qrcode_poster_template" />
             </androidx.constraintlayout.widget.ConstraintLayout>
 
         </LinearLayout>
diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml
index 35f279062f405b8ae168310b3da39c55130e4fb4..ad94c03c70d2a560903ab07561c0b82051a45b8d 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml
@@ -22,14 +22,14 @@
             <TextView
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:text="QRCode, PDF event registration" />
+                android:text="QRCode Creation, Poster Template download, PDF creation/printing/sharing" />
 
             <com.google.android.material.button.MaterialButton
                 android:id="@+id/testQrCodeCreation"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_tiny"
-                android:text="QR Code Creation" />
+                android:text="QR Code and Poster Creation" />
 
         </LinearLayout>
 
diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_qrcode_creation.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_qrcode_creation.xml
index 6ae5f29730ea651f8a8e8bc0d61f010427562518..7f8e4b10c71683b4cc812dcf7835de7076e9f037 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_qrcode_creation.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_qrcode_creation.xml
@@ -46,6 +46,16 @@
             app:layout_constraintStart_toEndOf="@+id/sharePDF"
             app:layout_constraintTop_toTopOf="parent" />
 
+        <com.google.android.material.button.MaterialButton
+            android:id="@+id/downloadQrCodePosterTemplate"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:text="Download Poster Template (result below qr code)"
+            app:layout_constraintStart_toStartOf="@id/generateQrCode"
+            app:layout_constraintEnd_toEndOf="@id/printPDF"
+            app:layout_constraintTop_toBottomOf="@id/generateQrCode" />
+
         <com.google.android.material.textfield.TextInputLayout
             android:id="@+id/qrCodeTextLayout"
             style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
@@ -54,7 +64,7 @@
             android:layout_margin="16dp"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/generateQrCode">
+            app:layout_constraintTop_toBottomOf="@id/downloadQrCodePosterTemplate">
             <com.google.android.material.textfield.TextInputEditText
                 android:id="@+id/qrCodeText"
                 android:layout_width="match_parent"
@@ -91,5 +101,15 @@
                 app:layout_constraintTop_toTopOf="@id/pdfTemplateImageView"
                 app:layout_constraintVertical_bias="0.25" />
         </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <TextView
+            android:id="@+id/downloadedQrCodePoster"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/pdfPage" />
+
     </androidx.constraintlayout.widget.ConstraintLayout>
 </ScrollView>
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt
index 1476022658f27fc6711345379d875e055c8098d3..f5504b7792dd600c4d2afa2a927967240c61d948 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt
@@ -34,6 +34,7 @@ class EnvironmentSetup @Inject constructor(
         DOWNLOAD("DOWNLOAD_CDN_URL"),
         VERIFICATION_KEYS("PUB_KEYS_SIGNATURE_VERIFICATION"),
         DATA_DONATION("DATA_DONATION_CDN_URL"),
+        QRCODE_POSTER_TEMPLATE("QRCODE_POSTER_TEMPLATE_URL"),
         LOG_UPLOAD("LOG_UPLOAD_SERVER_URL"),
         SAFETYNET_API_KEY("SAFETYNET_API_KEY")
     }
@@ -115,6 +116,8 @@ class EnvironmentSetup @Inject constructor(
         get() = getEnvironmentValue(DOWNLOAD).asString
     val dataDonationCdnUrl: String
         get() = getEnvironmentValue(DATA_DONATION).asString
+    val qrCodePosterTemplateCdnUrl: String
+        get() = getEnvironmentValue(EnvKey.QRCODE_POSTER_TEMPLATE).asString
 
     val appConfigVerificationKey: String
         get() = getEnvironmentValue(VERIFICATION_KEYS).asString
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/eventregistration/qrcodeposter/QrCodePosterTemplateModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/eventregistration/qrcodeposter/QrCodePosterTemplateModule.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5d7a0d8a163fecdda2d4b87c66453551bc88cc68
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/eventregistration/qrcodeposter/QrCodePosterTemplateModule.kt
@@ -0,0 +1,69 @@
+package de.rki.coronawarnapp.environment.eventregistration.qrcodeposter
+
+import android.content.Context
+import dagger.Module
+import dagger.Provides
+import de.rki.coronawarnapp.environment.BaseEnvironmentModule
+import de.rki.coronawarnapp.environment.EnvironmentSetup
+import de.rki.coronawarnapp.environment.download.DownloadCDNHttpClient
+import de.rki.coronawarnapp.eventregistration.events.server.qrcodepostertemplate.QrCodePosterTemplateApiV1
+import de.rki.coronawarnapp.util.di.AppContext
+import okhttp3.Cache
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import java.io.File
+import javax.inject.Qualifier
+import javax.inject.Singleton
+
+@Module
+class QrCodePosterTemplateModule : BaseEnvironmentModule() {
+
+    @Singleton
+    @Provides
+    @QrCodePosterTemplate
+    fun cacheDir(
+        @AppContext context: Context
+    ): File = File(context.cacheDir, "qrCodePoster")
+
+    @Singleton
+    @Provides
+    @QrCodePosterTemplate
+    fun httpCache(
+        @QrCodePosterTemplate cacheDir: File
+    ): Cache = Cache(File(cacheDir, "cache_http"), CACHE_SIZE_5MB)
+
+    @Singleton
+    @QrCodePosterTemplate
+    @Provides
+    fun provideQrCodePosterTemplateCDNServerUrl(environment: EnvironmentSetup): String {
+        val url = environment.qrCodePosterTemplateCdnUrl
+        return requireValidUrl(url)
+    }
+
+    @Singleton
+    @Provides
+    fun api(
+        @DownloadCDNHttpClient client: OkHttpClient,
+        @QrCodePosterTemplate url: String,
+        @QrCodePosterTemplate cache: Cache
+    ): QrCodePosterTemplateApiV1 {
+        val httpClient = client.newBuilder().apply {
+            cache(cache)
+        }.build()
+
+        return Retrofit.Builder()
+            .client(httpClient)
+            .baseUrl(url)
+            .build()
+            .create(QrCodePosterTemplateApiV1::class.java)
+    }
+
+    companion object {
+        private const val CACHE_SIZE_5MB = 5 * 1024 * 1024L
+    }
+}
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class QrCodePosterTemplate
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/EventRegistrationModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/EventRegistrationModule.kt
index 5b0bf95533d46f9c6b9aafd3a76e10f539abd342..2a1be8176e660b0e3b8c24a2b6af2739037812b3 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/EventRegistrationModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/EventRegistrationModule.kt
@@ -2,12 +2,17 @@ package de.rki.coronawarnapp.eventregistration
 
 import dagger.Binds
 import dagger.Module
+import de.rki.coronawarnapp.environment.eventregistration.qrcodeposter.QrCodePosterTemplateModule
 import de.rki.coronawarnapp.eventregistration.checkins.download.FakeTraceTimeIntervalWarningRepository
 import de.rki.coronawarnapp.eventregistration.checkins.download.TraceTimeIntervalWarningRepository
 import de.rki.coronawarnapp.eventregistration.storage.repo.DefaultTraceLocationRepository
 import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository
 
-@Module
+@Module(
+    includes = [
+        QrCodePosterTemplateModule::class
+    ]
+)
 abstract class EventRegistrationModule {
 
     @Binds
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateApiV1.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateApiV1.kt
new file mode 100644
index 0000000000000000000000000000000000000000..32b9d12cbeef24397a3f8c4a66987c6fc8eaec77
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateApiV1.kt
@@ -0,0 +1,11 @@
+package de.rki.coronawarnapp.eventregistration.events.server.qrcodepostertemplate
+
+import okhttp3.ResponseBody
+import retrofit2.Response
+import retrofit2.http.GET
+
+interface QrCodePosterTemplateApiV1 {
+
+    @GET("/version/v1/qr_code_poster_template_android")
+    suspend fun getQrCodePosterTemplate(): Response<ResponseBody>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateInvalidResponseException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateInvalidResponseException.kt
new file mode 100644
index 0000000000000000000000000000000000000000..12e67da2278110ed089cbf53106f66c9861eb9c9
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateInvalidResponseException.kt
@@ -0,0 +1,6 @@
+package de.rki.coronawarnapp.eventregistration.events.server.qrcodepostertemplate
+
+class QrCodePosterTemplateInvalidResponseException(
+    message: String,
+    cause: Exception? = null
+) : Exception(message, cause)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateServer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..dccb0432de5badcba1c0a4277a4ddd161284f24d
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateServer.kt
@@ -0,0 +1,65 @@
+package de.rki.coronawarnapp.eventregistration.events.server.qrcodepostertemplate
+
+import com.google.protobuf.InvalidProtocolBufferException
+import de.rki.coronawarnapp.server.protocols.internal.pt.QrCodePosterTemplate
+import de.rki.coronawarnapp.util.ZipHelper.readIntoMap
+import de.rki.coronawarnapp.util.ZipHelper.unzip
+import de.rki.coronawarnapp.util.security.SignatureValidation
+import retrofit2.HttpException
+import timber.log.Timber
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class QrCodePosterTemplateServer @Inject constructor(
+    private val api: QrCodePosterTemplateApiV1,
+    private val signatureValidation: SignatureValidation
+) {
+    suspend fun downloadQrCodePosterTemplate(): QrCodePosterTemplate.QRCodePosterTemplateAndroid {
+
+        Timber.d("Start download of QR-Code poster template.")
+
+        val response = api.getQrCodePosterTemplate()
+        Timber.d("Received: %s", response)
+
+        if (!response.isSuccessful) {
+            // TODO return cached or default response
+            throw HttpException(response)
+        }
+        if (response.body() == null) {
+            throw IllegalStateException("Response is successful, but body is empty.")
+        }
+
+        val fileMap = response.body()!!.byteStream().unzip().readIntoMap()
+
+        val exportBinary = fileMap[EXPORT_BINARY_FILE_NAME]
+        val exportSignature = fileMap[EXPORT_SIGNATURE_FILE_NAME]
+
+        if (exportBinary == null || exportSignature == null) {
+            throw QrCodePosterTemplateInvalidResponseException(message = "Unknown files: ${fileMap.keys}")
+        }
+
+        val hasValidSignature = signatureValidation.hasValidSignature(
+            exportBinary,
+            SignatureValidation.parseTEKStyleSignature(exportSignature)
+        )
+
+        if (!hasValidSignature) {
+            throw QrCodePosterTemplateInvalidResponseException(message = "Invalid Signature!")
+        }
+
+        return try {
+            QrCodePosterTemplate.QRCodePosterTemplateAndroid.parseFrom(exportBinary)
+        } catch (exception: InvalidProtocolBufferException) {
+            throw QrCodePosterTemplateInvalidResponseException(
+                message = "QR Code poster template could not be parsed",
+                cause = exception
+            )
+        }
+    }
+
+    companion object {
+        private const val EXPORT_BINARY_FILE_NAME = "export.bin"
+        private const val EXPORT_SIGNATURE_FILE_NAME = "export.sig"
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/EnvironmentSetupTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/EnvironmentSetupTest.kt
index 67dd22ff90fa82d7ff5ed8e4d7ab394770484ad1..44b3cabcf715d247d626b61b54f2dca00daa747d 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/EnvironmentSetupTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/EnvironmentSetupTest.kt
@@ -67,6 +67,7 @@ class EnvironmentSetupTest : BaseTest() {
                 safetyNetApiKey shouldBe "placeholder-${env.rawKey}"
                 dataDonationCdnUrl shouldBe "https://datadonation-${env.rawKey}"
                 logUploadServerUrl shouldBe "https://logupload-${env.rawKey}"
+                qrCodePosterTemplateCdnUrl shouldBe "https://qrcodepostertemplate-${env.rawKey}"
             }
         }
     }
@@ -124,7 +125,8 @@ class EnvironmentSetupTest : BaseTest() {
         EnvironmentSetup.EnvKey.DATA_DONATION.rawKey shouldBe "DATA_DONATION_CDN_URL"
         EnvironmentSetup.EnvKey.LOG_UPLOAD.rawKey shouldBe "LOG_UPLOAD_SERVER_URL"
         EnvironmentSetup.EnvKey.SAFETYNET_API_KEY.rawKey shouldBe "SAFETYNET_API_KEY"
-        EnvironmentSetup.EnvKey.values().size shouldBe 8
+        EnvironmentSetup.EnvKey.QRCODE_POSTER_TEMPLATE.rawKey shouldBe "QRCODE_POSTER_TEMPLATE_URL"
+        EnvironmentSetup.EnvKey.values().size shouldBe 9
     }
 
     companion object {
@@ -145,6 +147,7 @@ class EnvironmentSetupTest : BaseTest() {
                     "VERIFICATION_CDN_URL": "https://verification-PROD",
                     "DATA_DONATION_CDN_URL": "https://datadonation-PROD",
                     "LOG_UPLOAD_SERVER_URL": "https://logupload-PROD",
+                    "QRCODE_POSTER_TEMPLATE_URL": "https://qrcodepostertemplate-PROD",
                     "SAFETYNET_API_KEY": "placeholder-PROD",
                     "PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-PROD"
                 },
@@ -155,6 +158,7 @@ class EnvironmentSetupTest : BaseTest() {
                     "VERIFICATION_CDN_URL": "https://verification-DEV",
                     "DATA_DONATION_CDN_URL": "https://datadonation-DEV",
                     "LOG_UPLOAD_SERVER_URL": "https://logupload-DEV",
+                    "QRCODE_POSTER_TEMPLATE_URL": "https://qrcodepostertemplate-DEV",
                     "SAFETYNET_API_KEY": "placeholder-DEV",
                     "PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-DEV"
                },
@@ -165,6 +169,7 @@ class EnvironmentSetupTest : BaseTest() {
                     "VERIFICATION_CDN_URL": "https://verification-INT",
                     "DATA_DONATION_CDN_URL": "https://datadonation-INT",
                     "LOG_UPLOAD_SERVER_URL": "https://logupload-INT",
+                    "QRCODE_POSTER_TEMPLATE_URL": "https://qrcodepostertemplate-INT",
                     "SAFETYNET_API_KEY": "placeholder-INT",
                     "PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-INT"
                 },
@@ -175,6 +180,7 @@ class EnvironmentSetupTest : BaseTest() {
                     "VERIFICATION_CDN_URL": "https://verification-WRU",
                     "DATA_DONATION_CDN_URL": "https://datadonation-WRU",
                     "LOG_UPLOAD_SERVER_URL": "https://logupload-WRU",
+                    "QRCODE_POSTER_TEMPLATE_URL": "https://qrcodepostertemplate-WRU",
                     "SAFETYNET_API_KEY": "placeholder-WRU",
                     "PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-WRU",
                     "CREATE_TRACELOCATION_URL": "https://tracelocation-WRU"
@@ -186,6 +192,7 @@ class EnvironmentSetupTest : BaseTest() {
                     "VERIFICATION_CDN_URL": "https://verification-WRU-XD",
                     "DATA_DONATION_CDN_URL": "https://datadonation-WRU-XD",
                     "LOG_UPLOAD_SERVER_URL": "https://logupload-WRU-XD",
+                    "QRCODE_POSTER_TEMPLATE_URL": "https://qrcodepostertemplate-WRU-XD",
                     "SAFETYNET_API_KEY": "placeholder-WRU-XD",
                     "PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-WRU-XD"
                 },
@@ -196,6 +203,7 @@ class EnvironmentSetupTest : BaseTest() {
                     "VERIFICATION_CDN_URL": "https://verification-WRU-XA",
                     "DATA_DONATION_CDN_URL": "https://datadonation-WRU-XA",
                     "LOG_UPLOAD_SERVER_URL": "https://logupload-WRU-XA",
+                    "QRCODE_POSTER_TEMPLATE_URL": "https://qrcodepostertemplate-WRU-XA",
                     "SAFETYNET_API_KEY": "placeholder-WRU-XA",
                     "PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-WRU-XA"
                 },
@@ -206,6 +214,7 @@ class EnvironmentSetupTest : BaseTest() {
                     "VERIFICATION_CDN_URL": "https://verification-LOCAL",
                     "DATA_DONATION_CDN_URL": "https://datadonation-LOCAL",
                     "LOG_UPLOAD_SERVER_URL": "https://logupload-LOCAL",
+                    "QRCODE_POSTER_TEMPLATE_URL": "https://qrcodepostertemplate-LOCAL",
                     "SAFETYNET_API_KEY": "placeholder-LOCAL",
                     "PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-LOCAL"
                 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateApiV1Test.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateApiV1Test.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fe715bcdc2a27941e2f4c7206a274a39039a0637
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateApiV1Test.kt
@@ -0,0 +1,117 @@
+package de.rki.coronawarnapp.eventregistration.events.server.qrcodepostertemplate
+
+import de.rki.coronawarnapp.environment.eventregistration.qrcodeposter.QrCodePosterTemplateModule
+import de.rki.coronawarnapp.http.HttpModule
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.shouldNotBe
+import io.mockk.MockKAnnotations
+import kotlinx.coroutines.runBlocking
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import testhelpers.BaseIOTest
+import java.io.File
+import java.util.concurrent.TimeUnit
+
+class QrCodePosterTemplateApiV1Test : BaseIOTest() {
+
+    private lateinit var webServer: MockWebServer
+    private lateinit var serverAddress: String
+
+    private val testDir = File(IO_TEST_BASEDIR, this::class.java.simpleName)
+    private val cacheDir = File(testDir, "cache")
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+
+        webServer = MockWebServer().apply { start() }
+        serverAddress = "http://${webServer.hostName}:${webServer.port}"
+    }
+
+    @AfterEach
+    fun teardown() {
+        webServer.shutdown()
+        cacheDir.deleteRecursively()
+    }
+
+    private fun createAPI(): QrCodePosterTemplateApiV1 {
+        val defaultHttpClient = HttpModule().defaultHttpClient()
+        val templateModule = QrCodePosterTemplateModule()
+        return templateModule.api(
+            defaultHttpClient,
+            url = serverAddress,
+            cache = templateModule.httpCache(cacheDir)
+        )
+    }
+
+    @Test
+    fun `should perform request as specified`() {
+
+        webServer.enqueue(MockResponse().setBody("QR-Code Poster Template"))
+
+        runBlocking {
+            createAPI().getQrCodePosterTemplate().apply {
+                body()!!.string() shouldBe "QR-Code Poster Template"
+            }
+        }
+
+        webServer.takeRequest(5, TimeUnit.SECONDS)!!.apply {
+            method shouldBe "GET"
+            path shouldBe "/version/v1/qr_code_poster_template_android"
+        }
+    }
+
+    @Test
+    fun `should set ETag header of previously received response and return cached response`() {
+
+        // first mocked response returns a body and ETag
+        webServer.enqueue(
+            MockResponse()
+                .setBody("Poster Template")
+                .setResponseCode(200)
+                .setHeader("ETag", "ETAG_OF_MOCKED_RESPONSE")
+        )
+
+        runBlocking {
+            createAPI().getQrCodePosterTemplate().apply {
+                // we should receive the body and ETag
+                code() shouldBe 200
+                body()!!.string() shouldBe "Poster Template"
+                headers()["ETag"] shouldBe "ETAG_OF_MOCKED_RESPONSE"
+            }
+        }
+
+        webServer.takeRequest(5, TimeUnit.SECONDS)!!.apply {
+            // Our first request should not contain any ETag in the 'If-None-Match' header
+            headers["If-None-Match"] shouldBe null
+        }
+
+        // second mocked response returns 304 and no body (client already has latest poster)
+        webServer.enqueue(
+            MockResponse()
+                .setResponseCode(304)
+                .setHeader("ETag", "ETAG_OF_MOCKED_RESPONSE")
+        )
+
+        runBlocking {
+            createAPI().getQrCodePosterTemplate().apply {
+                code() shouldBe 200
+                raw().cacheResponse shouldNotBe null
+                raw().networkResponse!!.code shouldBe 304
+                // cached poster template should be returned
+                body()!!.string() shouldBe "Poster Template"
+                headers()["ETag"] shouldBe "ETAG_OF_MOCKED_RESPONSE"
+            }
+        }
+
+        webServer.takeRequest(5, TimeUnit.SECONDS)!!.apply {
+            method shouldBe "GET"
+            path shouldBe "/version/v1/qr_code_poster_template_android"
+            // Our first request should not contain an ETag
+            headers["If-None-Match"] shouldBe "ETAG_OF_MOCKED_RESPONSE"
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateServerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateServerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6f2a1b4b719753cf830a3f455dcfb938cc6a9b3d
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateServerTest.kt
@@ -0,0 +1,146 @@
+package de.rki.coronawarnapp.eventregistration.events.server.qrcodepostertemplate
+
+import de.rki.coronawarnapp.util.security.SignatureValidation
+import io.kotest.assertions.throwables.shouldThrow
+import io.kotest.matchers.shouldBe
+import io.mockk.MockKAnnotations
+import io.mockk.coEvery
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.verify
+import kotlinx.coroutines.test.runBlockingTest
+import okhttp3.ResponseBody.Companion.toResponseBody
+import okio.ByteString.Companion.decodeHex
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import retrofit2.Response
+import testhelpers.BaseTest
+
+internal class QrCodePosterTemplateServerTest : BaseTest() {
+
+    @MockK lateinit var api: QrCodePosterTemplateApiV1
+    @MockK lateinit var signatureValidation: SignatureValidation
+
+    /**
+     * Info: [QrCodePosterTemplateApiV1Test] is testing if the ETag is set correctly
+     */
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+
+        every { signatureValidation.hasValidSignature(any(), any()) } returns true
+    }
+
+    private fun createInstance() = QrCodePosterTemplateServer(
+        api = api,
+        signatureValidation = signatureValidation
+    )
+
+    @Test
+    fun `should return poster template when response is successful`() = runBlockingTest {
+        coEvery {
+            api.getQrCodePosterTemplate()
+        } returns Response.success(POSTER_BUNDLE.toResponseBody())
+
+        createInstance().downloadQrCodePosterTemplate().apply {
+            template.toStringUtf8().substring(0, 22) shouldBe "<vector xmlns:android="
+            offsetX shouldBe 10
+            offsetY shouldBe 10
+            qrCodeSideLength shouldBe 100
+            with(descriptionTextBox) {
+                offsetX shouldBe 10
+                offsetY shouldBe 50
+                width shouldBe 100
+                height shouldBe 20
+                fontSize shouldBe 10
+                fontColor shouldBe "#000000"
+            }
+        }
+
+        verify(exactly = 1) { signatureValidation.hasValidSignature(any(), any()) }
+    }
+
+    @Test
+    fun `should throw exception if signature is invalid`() = runBlockingTest {
+        every { signatureValidation.hasValidSignature(any(), any()) } returns false
+
+        coEvery {
+            api.getQrCodePosterTemplate()
+        } returns Response.success(POSTER_BUNDLE.toResponseBody())
+
+        shouldThrow<QrCodePosterTemplateInvalidResponseException> {
+            createInstance().downloadQrCodePosterTemplate()
+        }
+    }
+
+    @Test
+    fun `should throw exception if response contains invalid data`() = runBlockingTest {
+        coEvery {
+            api.getQrCodePosterTemplate()
+        } returns Response.success("ABC123".decodeHex().toResponseBody())
+
+        shouldThrow<QrCodePosterTemplateInvalidResponseException> {
+            createInstance().downloadQrCodePosterTemplate()
+        }
+    }
+
+    @Test
+    fun `should return default poster template when response is not successful`() = runBlockingTest {
+        // TODO
+    }
+
+    @Test
+    fun `should return latest cached template when response is not successful`() = runBlockingTest {
+        // TODO
+    }
+
+    companion object {
+        /*
+        POSTER_BUNDLE below encodes the following protobuf objects:
+
+        private val descriptionTextBox =
+            QrCodePosterTemplate.QRCodePosterTemplateAndroid.QRCodeTextBoxAndroid.newBuilder()
+                .setOffsetX(10)
+                .setOffsetY(50)
+                .setWidth(100)
+                .setHeight(20)
+                .setFontSize(10)
+                .setFontColor("#000000")
+                .build()
+
+        private val qrCodePosterTemplate = QrCodePosterTemplate.QRCodePosterTemplateAndroid.newBuilder()
+            .setOffsetX(10.0f)
+            .setOffsetY(10.0f)
+            .setQrCodeSideLength(100)
+            .setDescriptionTextBox(descriptionTextBox)
+            .setTemplate(
+                ByteString.copyFromUtf8("""<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+                "    xmlns:aapt=\"http://schemas.android.com/aapt\"\n" +
+                "    android:width=\"595.3dp\"\n" +
+                "    android:height=\"841.9dp\"\n" +
+                "    android:viewportWidth=\"595.3\"\n" +
+                "    android:viewportHeight=\"841.9\">\n" +
+                "  <path\n" +
+                "      android:pathData=\"M78.1,665.6v-12.8h1.2l3.6,8.5v-8.5h1.5v12.8h-1.1l-3.7,-8.7v8.7H78.1z\"\n" +
+                "      android:fillColor=\"#404040\"/>\n" +
+                "</vector>\n"""))
+            .build()*/
+
+        private val POSTER_BUNDLE = (
+            "504b03040a000000080014867d52008c85fefb000000ab0100000a0000006578706f72742e62" +
+                "696e7d90cf4bc33014c7071e949c0415bc0825bbc8685f9676edbad216440fbb78f61c9a6a8ae912da9089ff80ffb66957700e" +
+                "f185bcc3e7fbe3f0d0d7596eebcaa8cefb68e5aecfd88e77aae10516c6e88c90be1275cb7a983854aa254cbf93aeeec9c430f2" +
+                "dc4c71a6cdff59673804269aed1b6e4481e34d0c11d7bf3551376fc215a62b0a9b53d136f55eabcebc1c15fcedd81ed7e0d279" +
+                "72cd8c18bd3fee013d31c30afcbc4e81fa49124362031a422a28843282c44f21b6815b0ec47654020a540611ac7dc7d7d6fded" +
+                "90fec427edaf8d948f4aaaaec0f3d572789894282787eb97e86636f31eee86e5f1c5d505ba0c6fb9777d8fc2f3f9729c6f504b" +
+                "03040a000000080014867d528a1d0eac8f0000008a0000000a0000006578706f72742e736967018a0075ff0a87010a380a1864" +
+                "652e726b692e636f726f6e617761726e6170702d6465761a02763122033236322a13312e322e3834302e31303034352e342e33" +
+                "2e321001180122473045022100c251eb5e62282e5573fdb915edf61115d61d020354a510bed66b7b8ce482a38d02202a793775" +
+                "0958155c82a17acb6dd4b666afc1566285ef532e6e8c11e1d52e5a75504b01020a000a000000080014867d52008c85fefb0000" +
+                "00ab0100000a0000000000000000000000a401000000006578706f72742e62696e504b01020a000a000000080014867d528a1d" +
+                "0eac8f0000008a0000000a0000000000000000000000a401230100006578706f72742e736967504b0506000000000200020070" +
+                "000000da0100000000"
+            ).decodeHex()
+    }
+}
diff --git a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModelTest.kt b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModelTest.kt
index 7666b705e092c26da33a45c1f5fe515fa268c3c7..ed0342077389eb08c9590844454473015094eb94 100644
--- a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModelTest.kt
+++ b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModelTest.kt
@@ -1,6 +1,5 @@
 package de.rki.coronawarnapp.test.debugoptions.ui
 
-import android.content.Context
 import androidx.lifecycle.Observer
 import de.rki.coronawarnapp.environment.EnvironmentSetup
 import io.kotest.matchers.shouldBe
@@ -24,7 +23,6 @@ import testhelpers.flakyTest
 class DebugOptionsFragmentViewModelTest : BaseTestInstrumentation() {
 
     @MockK private lateinit var environmentSetup: EnvironmentSetup
-    @MockK private lateinit var context: Context
 
     private var currentEnvironment = EnvironmentSetup.Type.DEV
 
@@ -38,10 +36,10 @@ class DebugOptionsFragmentViewModelTest : BaseTestInstrumentation() {
         every { environmentSetup.downloadCdnUrl } returns "downloadUrl"
         every { environmentSetup.verificationCdnUrl } returns "verificationUrl"
         every { environmentSetup.dataDonationCdnUrl } returns "dataDonationUrl"
+        every { environmentSetup.qrCodePosterTemplateCdnUrl } returns "qrCodePosterTemplateUrl"
 
         every { environmentSetup.currentEnvironment = any() } answers {
             currentEnvironment = arg(0)
-            Unit
         }
         every { environmentSetup.currentEnvironment } answers {
             currentEnvironment