Applicants upload their previous certificate as a PDF. KEZA wants a download link to the file. The engine can generate a signed URL (or base64 content) for any file uploaded in the application. New idea: attachment markers.
Irembo sends an uploaded file as a group of flat keys sharing a prefix (the form field key), plus the application id:
Payload for the CERTIFICATE upload field
{
"applicationNumber": "C260926111500VICTR8",
"applicationId": "7b0c7d5e-1f7e-4c55-9d7a-5a0f0e9c2a11",
"CERTIFICATE.fileDownloadName": "C260926111500VICTR8_web_basics_certificate.pdf",
"CERTIFICATE.fileName": "web_basics_certificate.pdf",
"CERTIFICATE.fileType": "application/pdf",
"CERTIFICATE.source": "UPLOAD",
"CERTIFICATE.content": "",
"CERTIFICATE.fileSize": 48213
}
applicationId is required: the engine uses it to sign the URL.CERTIFICATE.* keys must be present, or the push fails with ATTACHMENT_MISSING_REQUIRED_FIELD_ERROR.Attachment markers: literal strings, not JSONata functions
| Write this string in the mapping | The engine replaces it with |
|---|---|
"generateAttachmentURL(CERTIFICATE)" |
a signed download URL for the file |
"getAttachmentContent(CERTIFICATE)" |
the file content in base64 |
"generateAttachmentURLFromrepeater(REPEATER, FIELD)" |
the URL of the file in the current repeater row (push iteration only) |
"getAttachmentContentFromrepeater(REPEATER, FIELD)" |
the content of the file in the current row |
1 · Request: POST /victor/keza/v1/documents
2 · Responses (the first matching rule wins)
Rule 1 · Document link received
| Match on | Key | Operator | Value | |
|---|---|---|---|---|
| If | Header | Authorization | equals | Bearer keza-victor-token-7f3a |
| and | Body | — | raw JSONPath | $.[?(@.document.url =~ /^https? |
Respond with 202 Body type jsonDynamic response ON
{
"documentId": "DOC-{{randomValue length=6 type='NUMERIC'}}",
"status": "RECEIVED",
"applicationNumber": "{{jsonPath request.body '$.applicationNumber'}}",
"receivedUrl": "{{jsonPath request.body '$.document.url'}}"
}
Rule 2 · Missing or invalid document link
| Match on | Key | Operator | Value | |
|---|---|---|---|---|
| If | Header | Authorization | equals | Bearer keza-victor-token-7f3a |
Respond with 422 Body type json
{
"code": "DOCUMENT_REQUIRED",
"message": "document.url must be an http(s) link."
}
Otherwise — when no rule above matches
Respond with 401 Body type json
{
"code": "UNAUTHORIZED",
"message": "Missing or invalid bearer token."
}
3 · Label
| Field | Value |
|---|---|
| Name | Victor KEZA Documents |
| Code | VICTOR_KEZA_DOCUMENTS |
| Service | KEZA |
| Category | victor-course |
| Active | ✔ served by the mock server right away |
document.url is an http(s) link (regex ^https?://.+) and echoes it back as receivedUrl. Anything else: 422 DOCUMENT_REQUIRED.{{randomValue …}} generates a document id, like a real system would.| Field | Value | |
|---|---|---|
| **Endpoint adapter *** | PMAPPING (push) |
locked after save |
| **Endpoint code *** | VICTOR_KEZA_DOCUMENT_PUSH |
locked after save |
| **Endpoint url *** | http://mock-service-engine.apps:8000/victor/keza/v1/documents |
|
| **Endpoint type *** | PUSH (set by the adapter) |
|
| **Endpoint name *** | Victor KEZA Document |
|
| **Partner *** | KEZA |
locked after save |
| Active | ON |
|
| **Endpoint description *** | Victor course: send the uploaded certificate as a signed link (attachment marker). |
**Endpoint Mapping ***
{
"method": "POST",
"body": {
"applicationNumber": applicationNumber,
"document": {
"type": "PREVIOUS_CERTIFICATE",
"name": `CERTIFICATE.fileName`,
"url": "generateAttachmentURL(CERTIFICATE)"
}
},
"auth": {
"type": "USER_CREDENTIALS",
"baseUrl": "http://mock-service-engine.apps:8000",
"parameters": {
"authPath": "/victor/keza/v1/auth/login",
"bodyTemplate": { "username": "irembo-integration", "password": "Keza@2026" }
},
"tokenConfig": {
"storageKey": "VICTOR_KEZA_TOKEN",
"headerName": "Authorization",
"tokenPrefix": "Bearer",
"expiryBuffer": 300,
"extractPath": "data.token"
}
}
}
CERTIFICATE.fileName. Without backticks JSONata looks for a nested object."url: " & "generateAttachmentURL(…)" is not replaced.Secrets · training literal. Hard-coded only because KEZA is a training mock. In a real integration use the environment variable instead: Keza@2026 → "${KEZA_PASSWORD}" (the whole quoted string, nothing before or after; see Lesson 3.4).
HTTP 200 captured on staging · 26 Sep 2026
1 · Request (curl)
curl -s -X POST "https://integration-gateway.staging.iremboinc.com/integration/v1/endpoints/VICTOR_KEZA_DOCUMENT_PUSH/mapping/test" \
-H "Authorization: Bearer $TOKEN" \
-H "portal: integration-management-portal" \
-H "Content-Type: application/json" \
-d '{
"applicationNumber": "C260926111500VICTR8",
"applicationId": "7b0c7d5e-1f7e-4c55-9d7a-5a0f0e9c2a11",
"CERTIFICATE.fileDownloadName": "C260926111500VICTR8_web_basics_certificate.pdf",
"CERTIFICATE.fileName": "web_basics_certificate.pdf",
"CERTIFICATE.fileType": "application/pdf",
"CERTIFICATE.source": "UPLOAD",
"CERTIFICATE.content": "",
"CERTIFICATE.fileSize": 48213
}' | jq '.data'
2 · What the engine would send
{
"method": "POST",
"body": {
"applicationNumber": "C260926111500VICTR8",
"document": {
"type": "PREVIOUS_CERTIFICATE",
"name": "web_basics_certificate.pdf",
"url": "generateAttachmentURL(CERTIFICATE)"
}
},
"auth": {
"type": "USER_CREDENTIALS",
"baseUrl": "http://mock-service-engine.apps:8000",
"parameters": {
"authPath": "/victor/keza/v1/auth/login",
"bodyTemplate": {
"username": "irembo-integration",
"password": "Keza@2026"
}
},
"tokenConfig": {
"storageKey": "VICTOR_KEZA_TOKEN",
"headerName": "Authorization",
"tokenPrefix": "Bearer",
"expiryBuffer": 300,
"extractPath": "data.token"
}
}
}
3 · What to notice
HTTP 200 1.60 s captured on staging · 26 Sep 2026
1 · Request (curl)
curl -s -X POST "https://admin-gateway.staging.iremboinc.com/integration/v1/push/sync" \
-H "Authorization: Bearer $TOKEN" \
-H "portal: administration-management-portal" \
-H "Content-Type: application/json" \
-d '{
"endpointCode": "VICTOR_KEZA_DOCUMENT_PUSH",
"callerId": "victor-course",
"requester": "victor-course",
"applicationNumber": "C260926111500VICTR8",
"payload": {
"applicationNumber": "C260926111500VICTR8",
"applicationId": "7b0c7d5e-1f7e-4c55-9d7a-5a0f0e9c2a11",
"CERTIFICATE.fileDownloadName": "C260926111500VICTR8_web_basics_certificate.pdf",
"CERTIFICATE.fileName": "web_basics_certificate.pdf",
"CERTIFICATE.fileType": "application/pdf",
"CERTIFICATE.source": "UPLOAD",
"CERTIFICATE.content": "",
"CERTIFICATE.fileSize": 48213
}
}' | jq
2 · Real response
{
"status": true,
"responseCode": "PUSH_SYNC",
"responseMessage": "PUSH_SYNC",
"data": {
"id": "5ca5fcec-edd4-49b5-bc6b-b26634ceab66",
"endpointCode": "VICTOR_KEZA_DOCUMENT_PUSH",
"callerId": "victor-course",
"message": "{\n \"documentId\": \"DOC-893907\",\n \"status\": \"RECEIVED\",\n \"applicationNumber\": \"C260926111500VICTR8\",\n \"receivedUrl\": \"https://integration-gateway.staging.iremboinc.com/application/v1/attachment/external?id=7b0c7d5e-1f7e-4c55-9d7a-5a0f0e9c2a11&fileName=C260926111500VICTR8_web_basics_certificate.pdf&accessKey=<redacted>\"\n}",
"adapterCode": "PMAPPING",
"applicationDetails": null
},
"timestamp": "2026-09-26T12:09:19.381444253",
"traceId": "6ab799ce59cb061c3ddb1d17e0e1024b"
}
3 · Decoded data.message
{
"documentId": "DOC-893907",
"status": "RECEIVED",
"applicationNumber": "C260926111500VICTR8",
"receivedUrl": "https://integration-gateway.staging.iremboinc.com/application/v1/attachment/external?id=7b0c7d5e-1f7e-4c55-9d7a-5a0f0e9c2a11&fileName=C260926111500VICTR8_web_basics_certificate.pdf&accessKey=<redacted>"
}
4 · What to notice
receivedUrl: the engine replaced the marker with a signed download link (its access key is redacted here).A partner wants the PDF inside the JSON as base64 in file.data. The upload field key is ID_DOC. Which mapping?
ID_DOC.content}A. Markers are quoted strings that must make up the whole value. The payload’s own content key is empty; the engine fetches the real content.
Your turn · break the marker, then fix it
Create
${YOUR_FIRST_NAME}_KEZA_DOCUMENT_PUSH(PMAPPING) on your copy of the documents mock. Replace the variables before saving (Aline →ALINE_…,/aline/…). First write the url as"url: " & "generateAttachmentURL(CERTIFICATE)"and push the payload shown above with a new application number: predict the answer.Done when: the broken version gets 500 PARTNER_ERROR_PUSH with
DOCUMENT_REQUIRED(the marker was not replaced), and the fixed version gets 202 RECEIVED with an httpsreceivedUrl. Do not paste the signed link anywhere.
← 4.4 Cache slow lookups: districts · 5.2 Repeated rows: one call per guardian →