Skip to content

Create personalised content

Before we begin

This tutorial assumes you have followed the quick start guide and are familiar with XPKit resources and how to call API endpoints with an Authorization header. We continue to use the EMEA region in the examples below.

Overview

XPKit provides the Visitor Created Content (VCC) service which allows creation of personalised videos and images for visitors. The service takes a media file and optionally transforms it in some way before uploading it to a destination.

In this tutorial we will take a generic image, personalise it and upload it to an Amazon S3 bucket.

Configuration

In the quick start guide we created a profile for Hideo Kojima and we registered him for the Game Developers Conference. Now the event is over we would like to send a personalised thank you for attending. We will take this image and transform it so the end result will look like this.

The first thing we need to do is create the configurations required. For every action in VCC we create a corresponding configuration. We will therefore define two configurations for our use case:

  • Create a composited image of:
    • the video game characters image
    • the photo frame and personalised text
  • Upload the new image to Amazon S3
XPKit Portal

Usually you would define your configurations in XPKit Portal by going to the Assets > Visitor Created Content > Configurations section. This is the recommended approach as there are a lot of available options and using Portal allows non-technical people to set up your configurations. For this tutorial we will configure everything via the API endpoints.

Image composition

Each configuration uses an adapter; an adapter can be either a transformer (it changes the asset in some way), or a destination (it uploads the asset somewhere). A full list of adapters is available for later reading, for now know that we need to use the image compositor adapter.

The way this adapter works is it takes an image you upload and then uses it in a HTML document you provide in the configuration. This HTML document is then saved out as a PNG file. Think of it like taking a screenshot of a webpage.

In the HTML you can use personalisation tags that will be substituted during image composition. Looking at our desired output we need to use a profile's {{first_name}} field. We can reference our image using the {{image_url}} tag.

Here is the HTML we need to achieve our desired composition.

We can now create our first configuration resource:

{
    "adapter_config": {
        "defaults": {
            "height": 720,
            "html": "<!doctype html>\r\n<html>\r\n\r\n<head>\r\n  <meta charset=\"utf-8\">\r\n  <title>VCC template</title>\r\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\r\n  <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\r\n  <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\r\n  <link href=\"https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@300&display=swap\" rel=\"stylesheet\">\r\n  <style>\r\n    * { margin: 0; padding: 0; }\r\n\r\n    html, body, main {\r\n      height: 720px;\r\n      width: 1280px;\r\n    }\r\n\r\n    body {\r\n      background-image: url('{{ image_url }}');\r\n      background-repeat: no-repeat;\r\n    }\r\n\r\n    footer {\r\n      font-family: 'Roboto Slab', serif;\r\n      font-size: 32px;\r\n      align-self: flex-end;\r\n      flex: 1;\r\n      padding: 0 100px 70px 0;\r\n      text-align: right;\r\n    }\r\n\r\n    main {\r\n      background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABQAAAALQCAMAAAD4oy1kAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAKjUExURQAAAFtbW2pqan9/f0xMTHl5eV9fX////6qqqmZmZpmZmVdXV2pqal9fX25ubn9/fwABAhEQD39/f2JiYmFhYVxcXI+Li19fX3Z2djU1NXt7e3h4eDc6PmdnZ1VVVQUFBWlpaXd3d21tbWlpaW5ublBQUHNzc1VVVU5OTktLS0lJSW1tbWBgYHFxcVxcXFVVVVJSUpGRkWtralVVVVRUU2pqaVZWVlVVVWhoaHR0dG5ubm5ubj09PkBBQllZWTY3N2dnZ29vbzk7PgAAAA8QEAcICAICAQEBAAcGBDEwMh8fITEwMw4QEAAAAREREQEBAQMEBAICAwgKCgwNDQoKCx8hIScoKQwMDTEzMwcHBy4wMC8xMQoLDAUGBgUGBwQFBQgJCQUFBRcZGQABAg4PDwcICAIAABMTFAsMDAQDAi4wMy4wMg4ODhYWGC0wMgIDAwoKChEREicmKTIxMwsLCx0cHiQjJQQEAxgZGhAQECopKzQ3NyorLSUnKC8tLwMCAS0uLw4QERUXGDAvMTw+PhcYGQ4PEBASEh4gIS4tMBEUFBobHSstLyQoKSwxMgQEBgECAhcXGREREw4NDhISEBIREQ0MDBESECEiIzAzMyQmJwwODiAhIjEzNDAyMhwdHhAREiMjJCkrKwUHBysuLSMkJhESEggIChQWFy8xMxMUFSYoKC4wLy0vLwgICScoKCstLTE1NQoMDAoLCx4gIAUHBjAxMQ0PEA8PDyMmJwsODgoKDC8wMwUFBistLi4xMyosLyAiJjI3NhobHxESFiUnKxweIgwNEQ4PFBQUGBYXGzMxNDo7OzY4OTQzNScpK01PUQUEA0NFRgEAABkaGy4uMBcaGiwsLzM1NigsLAAAAhYYGBYYG19kZS0zMxIUFhQWGWTKsAgAAABDdFJOUwAcNwhDFRABAwUFIwwILAz+/hBDFSw3MxxyIzNyRRX+Ig8qM0MzCxsqPUUHPRsLDyIHT1VKXmY4PhcfO4OOLngbJXjlWSpgAAAcQUlEQVR42u3di1+Vdb7ocbPyZJpzzjid5kzHvU27X+aiVpa7217iTvNCQuAFFIvX4CUvWXlnEsUrzpRabQQBhRCkkURsMkurXVNTgSxXmPRy3M6fcp5nLUS0mqk5LUR6v18KyEuetV7P83p9Xt/f8zys1au6YMbkCywK/gD0FOmdzM7cvj03d3TBxOXb8rJ6ZZxYk5rSeKHnAHqKrA6ZmWmlQf6W79q2ofZUTqRXdWXzG+kz0gB6su2hp9evX778mT15dXW1zz95PBIEcMkbzc21s0rX5wL0QIcOrV9/6NCh5QUT9+zJC9o3f82pU63F2yKRMID5K/LLY4vTM5/Z1e4ZgJ4kzNq2sH218+dvXdba2jquenskEcCSjMo3phU2z38ury7vo1BdIA+gp6j7qDawdeuyIH4VFRXjFo+bETkXwFX79zfFSp7Om39qPkBPsTW0bt26MHytRUH6Do4bt7M4f/XWSLteu5e80VK5f9qJtljz1m3L1rVbBnCpa40rCtNX8fy4ceOKi5dUV69euydyLoDV+WEAy8unx2IlrywrKor/5+cBLnXx8B0Mxr5w8Nu9ZEn+G/lvNC8+EjkXwOLdJS0tq45On1K2vzna/NmycQA9w85QcTD3FVdX5y9evGLFinmx3ZFOeu3cXfJ6S2UQwClTpjXEYotbdweWAPQIJSUli+PtW3Xy5MmmprrI+QEsLsnPCNbA08t3/Ff5vsJo284liwF6hBVxq07+7sTYZ9d+Hju2NHJhAKsXBwH84/Ty8h1lZdPaorFVS8If6GwFwCUmUa+TJ3+36cTaxz7/vO2xwuiKSGTMhQFckp/Rsn/f9OnTy6dMKXtvf2G0afHJTZs2nWi3KeF3AJeMsFon1q5dO/axZz8/duxYc/OxaCwv6N+kbwrgqn3TggDumDKl4U+vH43Fxq4+9nmHZzuMBbgEJIoV5CtoX1tzc1NTc2E0djyycMykrwWwOv/1jFX7/jgtPA24Y8q01fkVzdGm3x3r8PUQAnRr8WaF/VrZHPZvZSw6NhJJHfNNASx5PeOrRACDAk47+lXx1t2xwrXPXhDAzht/DKCb6ihgewCbmo5Fo/MjTy1d+E0B3B0EsLI9gOVhAKt3nqptiDW/e+EQKH7AJZLATv071hSNTY3MmLnwHwRweiKASzYeXPPWwcLCz78+Ajq3AFwSJwHjI2BbEMBnY9HKyBdTZ317AN/46mg8gNMTE+DGitba0twFhSvHJupXFh/8wq2uBej+xoZXf4MZbuXnz0aj2yJHUrJnzfqOAVwSBrCutH5jU9Pad8s+jy96x86bF2z0vBtjALqnoFNhBMuOrW6OFlZFalKq/m4AK1/rFMCSMICbP8pNzVpQ2Da2rGzv3vLy8nnT2+8LXA3QzQWpml6+t3x1YfSDSOrk+u8fwA1vHT7y1MGm5r17y/ZOnzdv3okTCxa0b3sLQHdztMOWLUECF8zbMjcWK43k1NR8jwCu/qpkZ7gEDgKY9WbknZamsmlz1y7Yd2L1iS2rjx59P7QKoNuprKxsCVQGX77//tETLWWx/Mcj6Qe+XwD3lxQfbF1Tt2394XdSsiOR1r0nvkpstiUjIyMfoJsp6VAdCD/nZ5QUNxdujsysmdz4nQMY3gazelV+8cGiNWe2jQ4DmPpF5ED+qvAlsnYXx+18IrBx4xMA3cLGjQfjKs45+ERRRWHhq5GqqvrJWYkAHpmV+l0CuO+18wL4eCTyVEXLuPAhElsuAuhmWs9XtKsitmPqzKrsswFMqfq+ASwoTQ8DOOGpSKT2g3D7y5Yt+3LdujVx4RuOrAHoDhLvgVTb7m91Wb+Pvl91oDE7uyonDGBQwKnZR5am/sMATgkDuDsIYF0QwMZEAF96KnKgYl1dbe2ZM2fqNgN0RxsS9mwurV8Zzc+dWDp5VhDA9KyayTU53zWAf1r92uIPnih68kynAD6eOqGqdk1Bwa63CgC6mfEJBePXr1//1vjcmQWxaHXrl3tKJ2dnpwQBTE8PR8AZR5Yu/Q4B3Lc/CGDFungA6xMBXJg68/GZG+YXlLY/TvgwAN1LbmD8gZeKo9GM4iee7BTA9O8YwB3xAFYHAazNCyfAGWEAHw8CeCQ7NW1d3tMFo8cDdBe5FxhfNbMpWrjjaP7GjgBmvtMYD2D29wpgXXsAJ7UHsCo158s1uYcKRmsg0C0TWPD0mM3R6PSy5tXnBTDrnwnghtKs8wJYlZ1dV7End+LE5YHRAN3CoUC8f6MbJ6w4HdvS0BwE8Imz5wDfOZyVlT75BwhgSsqRiRV5uRM3JBq4XAiB7lHAeP+yc5pON7/ftnLl2QAeCQOY+U8EcFlt3ba3DlwYwJzs2RVPjh69Z2InUgh0rQuKEw/g+NLUomj0xLy2IIAr97UH8M3OAZz53QKY0TmAC88LYE5KzpMVryzfE/paAgG6zOjlnZfAo3OOn4gWntx77NiFATz8AwZw8nM5GypOLd+Tl7fn/AoCdJnlyztNXmEBj0+MRcu2HCsLtCUpgEH/Fs1OS1k0vzXvmbw9iQROFEGg6wt4bu156NDyzKVLorEtc+P9CwN49AcL4FPnBbAxLW121YyJFXkT8wIvnxV/Rv8JkGRfv/5waHROzrPRlVsaysLXrw8C2LbyaEn8NpgfNIBB/4IAls5Or5nZuG7dK3l55zXwlVdeBkiyV155ZeLL5199eHR+YeHblcfC/AXKGoIAvv3/HcBd5wVwRkpV2L/n0krT0mvqs2f957K6V/KCpxI+HYCus3x5+8fw5N/yV+t3F77/ccu89v4lK4Ap8QCW5qY1hhtbWDO/Nj70BU/kfwF0scTFj0M5y1e1ffzhn1s2fR4E8L0kBTC7PYCzgwA+F2xs6owxS5fnLX/60Pr165/eDtCVXn0189XQoqnL5lWf+fCDt1u27C3b+9577767tzwewIPJC2B6GMAjsybVb9vz6va00lfjTyf8C9BVZs+e/dzTz7ecKdj68c4wgMEAODdZAazqCGBpVnpOPIAzIzUPPPDAvwUeeuihf3/oof8DkHQPxQXhCfrzwAeHP/n0rx/ufPvP778b9G/uvKCAZQ1JDeDsxkQAZ00ac3dg2LBBg24cOWpUb4AuMGrkyBsHDRo2fPjd99xT8MlHQQA3VgcBfC8M4LwwgP+VpAAeSAsDmFXTHsCF9wSGDx8+YsSI20fcfgVA8t0eFCfoTpife/7SEcC5QQAXdGkAJ9199z1h/gbeeee9gZuuA0iqm8LW3HvbFWED2wP42YfjPogHcEFyA9h4IO3pQ08HAczpCODdw0YMHHhv7+uuezBwLUBShaUJKnhbkMCwgIkJ8OMwgHPndlEAJ5+bAIcPG3jnvdeF6esH0AVuvfXBB2+6N17A4RclgDkpQQD/NQjg8BGDBob96zd48GUASTf4sl/363dtWMDb2wP4WXsAF3R1AIcNGzZw5HXXDhk8+OGH/yX0M4CkiWfmliCBtwYFDEfAzgFM+gQ4uyOAVWEAI8OGjRjZe+iQfvc/HDy1PgBJF1Twll//+tYHgxFwxEUO4KBgABwwOOhfnxtu6A+QdDcEDbzlsn633hQEcESnAL7XRQGc3TmAo4YOGRz0r0//X14DkHS/7N+nTzAD3hqsgW8f0ekiSFcGcGo8gIMGDew94JH7H77yhp/cfPkdd/QFSKo77rj85v7xEfDBswHs2gkwMwxgSiKAN44MA3hfnxtuvrxv36uvAkiqq/vecc3Nv+oTroHvve322y9qAAfeOHLogEcevu+n/a+5I56//wGQNGFl+l5zTf9gDdzv1puuuOL2izwB3hmeArwyGACvVj+gKxrY95pf/ip+EvC2ix7AkUEA7woC+Jurg/71AkiqoIB977joAZzaaQKMB7CvAAICCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAujgAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIAODiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAggggAACCCCAAAIIIIAAAggggAACCCCAAAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgLo4AACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAggggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCADg4ggAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAIC6OAAAggggAACCCCAAAIIIIAAAggggAACCCCAAAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIIIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggA4OIIAAAggggAACCCCAAAIIIIAAAggggAACCCCAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAiiAgAAKICCAAggIoAACAujgAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICKICAAAogIIACCAigAAICCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggggIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAACiAggAIICKAAAgIogIAAfj2AKYkADhp5NoBXCyDwIwzgw0EAL7/6qqCAAMl11VV9r+kuAQyWwAMeefi+n/a//I6ggBoIJLd+V1119R3X9O/zL7f0u/WmKy5WAOs7Ath7wCP339cnGAH79o0nECB5ru7b95qbf9Xnlsv63XrvRZoA088FcGAYwLuuvOEnN//mN32vBkiqvr+5/Ob+N/wsCOCD914xYsRFDeCwQQNHhScBgxHwJz+5HCDpbu7fp8/PLut37U3dIICDggD+IhgBf/rTIIEAyXbDDX1+dtf9g6+97lwAN3Z5ALPjARw2aGTvAUOCRfB9VwYNBEiyPldeed8t9/e79sF7bxsxYnj7BFjdtQFMORvAYTeOGjogmAF/ftd9oSsBkiaembt+fv/gIdded+cVI4bHA/jXDzcGAXz3vblzuz6Ag+7sfX1QwF/cf//PAZLv/sGPDBkw9N6BIy5GAHPTMhMBnDUpcvfd4SJ41PUDBgwZ8sgjvwBIskceGTJkwIChve8cGPRv+F8KPvq09eOdb7e8X5b8AB4IA5iZmTgHOCMMYFjAG0f1Hho0EKALXD90aO+RIwcOGz78nns++uSjv7V+/EEQwL1dE8D2G6HDCXDS3fEC3njjqFGjevfufT1AUg29vnfvoDejBg2K9++eYAn8WVEYwC179+6dO7cLAjg76+wSeFLw+PEEDhsxaOCggQDJFpZm0LBw+XtPGMCCM3/rqgBOPpBWmhsEMPFyWLMmzUnUb+T/BuhKN94YpCcI0Jm/fPrXIIB/XrWpbG+wCJ73bnICOCMewMbZpbml5wJY88ADD/xb4N/j/i9A0iV6E4Qn6M8DH2/4qHMA3+2KANbkTJ1xZNbSIy8+FQG4SCZEHv/kw88++3BcEMATn5clL4BHOgewsSYlCGB2VaoDAFxcL372YSKAZckMYHZVVU7O5MbnSnPTnksPVsAzqma8NGHCU3H/E6CLJeozITLhLx98cDaA7wX9Ky9rWJmMAMZHwLTStMZF9SkpKamRCQAXWyRS80FL/qoFHQFs+EECuO3rAcyZvCgtbfbkmpT6GRP0D+geCZz04dz3N5WFV0H27g1WwG3JCWBYwNlps+trDqREIi899RLARfNUh0jkb01lCxqCAv7gAZx0YQBzckanP/7ikRcBLq7UhJkLI2cKCxe0B7AtDODG5ASwMefQp2kzcmpqanLSAS6aMEMdJmQWxk7EExifAEuSE8CUqjUHN2wvKNiV8MyuZ7Y9s2sbQFd65plnggAVFBSsX58bKl1fU7My2vZ+WaBt5cp9+U98GQTwzR82gJNTcw4W156ZH7f1rHWBrQBdbP782tq6urq8uIKcymiscu+xspXJCuCEtFX5Rc+PA+gWDj5f8XxFRVHrstat62q3F0WjJzcF/Wue9vudrUEAq37AAKakRk61ra7OyHg9LqNdS0sGwEWRn59fUlJdvXt3cfHOnTvnr4mebqtsbm7+Q8bO1m0dATyQ9f0CuLPiy68H8KUxR2Nt08t2nK+ssykASXWuN2GAppeXT58+bdq+o0f379//1dH8g7HTsZbmpvdaiovqcuMBzNoeBDC9JqXq+wRw2akzuzoHcMbMyOHCaGFCU4dCgIvkXImamkPB4rdtReHp0w1tO8IAjm+MBzDtnwngmjMbOgUwe1JkYyzWfPZBALqHtraGhoZw/bljRzANlk+pfO306VhGS3VFbUEYwJoDb2V+rwC+tjgIYOuaM3sKzgUwMvMPsZX/3fDfoT8AdC9btmz5KmFLS3Hh6Wjxzoo1BY0z3kyZnFn6vQI4rT2AtZvfyqxP/CpcJDK+oTm+7ZaWlt8DdCdvd6iurg4+Fr19Olp96tSGA0EA0w+XZmZ+5wCWle97LePtswHMCQP4UiRStKNl59kLLU8EDm4E6A4Odqhod3Djl7WF0ZZDe9KygwCmJQKYk/IPA1i+Iwzgvozq4oNFT9bmFaTVhAGMLKzIqGitKAq0ti5b9mRoHUB3cerUqTWdPVmXVhzbUroo5c2crMPj0w5nNSYCuPAfBHBKWfm0o4tLggB+WVsXD+CYSP2XRXl1oc15m/P2xG0A6E4mxhUUxD9t2LBn0YZ9laWpKZOzStenHc4MAzjj2wP41dGOAE7/44qS3RuLls2v21U6ueqlhVl1y7evX38otzS3dPv2tLS0TIBuZHa7rITgq+CbaTkpy4pLUycfKC0oTcsM38htxqxvD+C+MIBB/8qmJALY+uX8ug256UfqM2fnnJMSNxWgO6qqav8iJaU+JfW5UxPfqX+roLQ080D63wtgRmUYwPIdZWVtU8qnrcgPbwRcV7vhcGbmohfHzEpYGpqzdM5CgG5oTkL7P4Je/XbM9tqCTwreCkbA9Mn1U2fN/PsBDAbAtmPl01b9PrwPZl3t5jNpcyKTxgBckr6IzHn51K715wK48JsDuGpfeBNM+EKCbVN2nGzJL64oWrastt5b7gGXtkWfbsstzWz81gBWnw3gjngAyxIBrNi46wv7Drjk3z199ubxmVnfGsAl+Rkt+/e1D4DNK9saNlVmVFcfzLTjgJ6g6q31WYtyUo7M+tYAhvfAhP1rbm5rGLuqZXXRGHsN6BleysptzEnJ/sYAVi/OqNw/fcqUsrB/TUEBTyxoaLXLgJ6jfvu3BbDk9ZbKo0EAG8L+NTU1tzU9dsj+AnqS48/VfFMAi3eXBAPgvvKGtrYwf4VNTbG1C+0toGd5ISfnGwOY31K5f9qU+Po3fJHV2BuRyAR7C+hhpr6Z+rUA7l6SUblqdXlDe/+ihS9HIv/xxTn/AXApa29ZJDLr0YWR8y/v9qpOrIDb2vvXdDwS+W3HD553U/ULAN3ft/5iyITjUxdeGMD8FUf3TStrDk//xaLB8veFc72bA9Bz/PZ4yvHzA1jyxqrV0/7UFsx/TdHoy5Ev5rwQL9/xQOJFEB4FuFTNOs+jjy5687wA5q/Yv+9PU1YGA2C08NHInDkvhAVcenyWAAI9L4CP1h/ptArulbHi5LwpwQAYiz0RiZw739dpalwKcGm6YA0cfuv4o+cK2Kty1YnyY8eaooWLgv6dd+nkt15MB+h55swZcy6A+6eXlRXGSiKRSRfeNeM2GKCH3ARzXs7GnK1dr/2rxzbECl/9hv4B9Gy9jp5si+2TP+BHGcDmplr9A36UASw/8aa9APw4A1ix0PgH/Dj9PxlFN/oLDgZAAAAAAElFTkSuQmCC');\r\n      background-repeat: no-repeat;\r\n      display: flex;\r\n    }\r\n\r\n  </style>\r\n</head>\r\n\r\n<body>\r\n\r\n  <main>\r\n    <footer>Thanks for visiting {{first_name}}!</footer>\r\n  </main>\r\n\r\n</body>\r\n\r\n</html>",
            "profile_data": [
                "first_name"
            ],
            "width": 1280
        }
    },
    "adapter_name": "compositor",
    "configuration_name": "gdc-compositor",
    "experience_id": "game-developers-conference-2024"
}

We have set the width and height to the same dimensions as our image and specified we would like to be able to access the visitor's first_name field from their profile using profile_data.

Request size

The VCC has a request size limit of 16kb. In the example above we base64 encode the photo frame image which makes the request around 12kb. If you go over this allowance in your configurations you should load images externally and reduce white space characters in your HTML where appropriate.

We can save the resource by using the create configuration endpoint:

import requests

endpoint = 'https://vcc.emea.xpkit.net/api/configuration/'

headers = {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json'}

resource = {
    'adapter_config': {
        'defaults': {
        'height': 720,
        'html': 'HTML template here. Omitted for brevity...',
        'profile_data': [
            'first_name'
        ],
        'width': 1280
        }
    },
    'adapter_name': 'compositor',
    'configuration_name': 'docs-compositor',
    'experience_id': 'game-developers-conference-2024'}

req = requests.post(headers=headers, url=endpoint, json=resource)
result = req.json()
async function postData(url, data) {
    const response = await fetch(url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
        },
        body: JSON.stringify(data),
    });
    return response.json();
}

const jsonData = {
    'adapter_config': {
        'defaults': {
        'height': 720,
        'html': 'HTML template here. Omitted for brevity...',
        'profile_data': [
            'first_name'
        ],
        'width': 1280
        }
    },
    'adapter_name': 'compositor',
    'configuration_name': 'docs-compositor',
    'experience_id': 'game-developers-conference-2024'
};

postData('https://vcc.emea.xpkit.net/api/configuration/', jsonData).then((data) => {
    const result = data;
});

Image upload

Next we will create our configuration to upload the asset to our destination. This configuration uses the Amazon S3 adapter and it looks like this:

{
    "activity_config": {
        "activity_type": "asset-creation",
        "experience_id": "game-developers-conference-2024",
        "payload": {
        "object_id": "souvenir-generator"
        }
    },
    "adapter_config": {
        "auth": {
            "access_key": "YOUR_AWS_ACCESS_KEY",
            "secret_key": "YOUR_AWS_SECRET_KEY"
        },
        "defaults": {
            "bucket_name": "example-bucket",
            "bucket_url": "https://s3.amazonaws.com",
            "file_prefix": "gdc-2024/",
            "force_download": false,
            "public": true
        }
    },
    "adapter_name": "s3",
    "configuration_name": "gdc-s3",
    "experience_id": "platform-team-testing"
}

This configuration has an activity_config object as the adapter will create an activity containing the Amazon S3 URL for the asset. The values for the fields provided in this object will be used in the created activity. Note that all destination adapters create activities.

Ensure the AWS credentials you supply have read and write access to the bucket you provide.

To save this resource, call the create endpoint in the same way we did for the image compositor configuration.

Chains

Finally we need to create a chain. A chain lists all the configurations we require and the order in which to run them. Our chain will look like this:

{
    "call_chain": [
        ["gdc-compositor", null],
        ["gdc-s3", 0]
    ],
    "configuration_name": "gdc-chain",
    "webhook_url": "https://events.gdc-conference.com/callback"
}

Here we specify:

  • the first configuration (gdc-compositor) should take the original image as its input (null value)
  • the second configuration (gdc-s3) should take its input from the compositor (0 index in the chain)
  • the chain should be called gdc-chain
  • once a task has completed using this chain the result should be posted to a webhook

Save the chain by calling the create endpoint once more.

Tasks

Now everything is configured we can trigger tasks to run these configurations to create our images.

To trigger a task we need our chain name (gdc-chain) and the profile we are creating the asset for. We can specify the profile by providing either a: profile (resource) ID, email, RFID or QR code. As we know Hideo's email address we will use that.

The task request will look like this:

import json
import requests

endpoint = 'https://vcc.emea.xpkit.net/api/task/'

headers = {'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}

data = {
    'configuration_name': 'gdc-chain',
    'extras': json.dumps({'email': ['hideo.kojima@kojimaproductions.jp']})
}

files = {'data': open('/path/to/vcc-characters.jpg', 'rb')}

req = requests.post(url=endpoint, headers=headers, files=files, data=data)
result = req.json()
async function postData(url, data) {
    const response = await fetch(url, {
        method: 'POST',
        headers: {
            'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
        },
        body: data
    });
    return response.json();
}

const fileField = document.querySelector('input[type="file"]');
const formData = new FormData();
formData.append('configuration_name', 'gdc-chain');
formData.append('extras', JSON.stringify({'email': ['hideo.kojima@kojimaproductions.jp']}));
formData.append('data', fileField.files[0]);

postData('https://vcc.emea.xpkit.net/api/task/', formData).then((data) => {
    const result = data;
});
curl --location 'https://vcc.emea.xpkit.net/api/task/' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--form 'data=@"/path/to/vcc-characters.jpg"' \
--form 'configuration_name="gdc-chain"' \
--form 'extras="{\"email\": [\"hideo.kojima@kojimaproductions.jp\"]}"'

You'll receive an acknowledgement response and the composition task will begin.

Upon receipt of a callback to the webhook URL you provided in the chain (or you see the task has finished in XPKit Portal under Assets > Visitor Created Content > Tasks) head over to XPKit Portal. Navigate to the Visitors > Activities section. You should see a new asset-creation activity for Hideo. It will look like something like this:

{
    "activity_type": "asset-creation",
    "created": "2023-07-10T05:48:27Z",
    "experience_id": "game-developers-conference-2024",
    "last_modified": "2023-07-10T05:48:27Z",
    "owner_id": "c48ea5a2-1f6e-4772-a23f-c4bc09732b47",
    "payload": {
        "object_id": "souvenir-generator",
        "souvenir_data": {
            "adapter_response": {
                "bucket_name": "example-bucket",
                "file_key": "gdc-2024/1688968106_1688968101_vcc-characters-composited.png",
                "url": "https://s3.amazonaws.com/example-bucket/gdc-2024/1688968106_1688968101_vcc-characters-composited.png"
            }
        },
        "task_id": "61b8a658-e6ba-4a14-829e-72d0b060fc28",
        "vcc_config_name": "gdc-s3"
    }
}

The URL under payload.souvenir_data.adapter_response points to the newly created personalised image.

Notifying visitors

A common workflow is to create a notification rule (like we did in the previous tutorial) to send an email containing the asset to the visitor. To do this:

  • Update the activity_config object in our Amazon S3 configuration above to set an activity_action.
  • Create a rule so when a matching activity is created by the VCC a notification is triggered
    • The rule can be configured so the image is sent as an attachment, or...
    • If you wish to use the image in the template itself you can reference the URL using the {{url}} tag. All fields in the activity under payload.souvenir_data.adapter_response are available.