DeploTeka Set up your first store
Developer docs: Token delivery

Token delivery

For a product that runs on your servers and calls the Admin API. When a merchant installs their dedicated app, DeploTeka sends you the token. You never ask the merchant to copy one.

What happens

  1. You add a store in DeploTeka. It creates a dedicated app and sends the merchant one install link.
  2. The merchant installs and approves the permissions your app asks for.
  3. DeploTeka posts the token to {your address}/api/deploteka/token. You set the address and a shared secret on the app’s card.

The request

POST {your address}/api/deploteka/token, 15 second timeout. Headers: authorization: Bearer <your shared secret>, idempotency-key, x-deploteka-event-id, x-deploteka-correlation-id, x-deploteka-contract-version: 1. Check the bearer before you read the body.

{
  "contractVersion": 1,
  "eventId": "evt_01J…",
  "correlationId": "cor_01J…",
  "event": "token_issued",
  "eventReason": "initial",
  "shop": "client-store-shopify-demo.myshopify.com",
  "clientId": "a1b2c3d4e5f6",
  "accessToken": "<offline Admin API token>",
  "apiSecret": "<the dedicated app’s API secret>",
  "grantedScopes": [
    "read_orders",
    "read_products"
  ],
  "installedAt": "2026-11-07T10:00:00.000Z"
}
FieldNotes
eventtoken_issued, token_reissued or token_revoked.
accessTokenThe offline Admin API token. Absent on token_revoked.
apiSecretThe dedicated app’s secret, for verifying Shopify webhook signatures.
grantedScopesExactly as Shopify returned them. Don’t reject an array that is not sorted or de-duplicated.

Your answer

Answer 200 with JSON that echoes the event:

{
  "contractVersion": 1,
  "eventId": "evt_01J…",
  "correlationId": "cor_01J…",
  "status": "applied",
  "receiverOperationRef": "rcv_op_01HXYZ",
  "ackSchemaHash": "sha256:<64 lowercase hex>"
}
  • status: applied, or replayed when you already stored this exact event.
  • receiverOperationRef: your own record’s ID, 1–128 characters of A-Z a-z 0-9 : _ -.
  • ackSchemaHash: sha256: and 64 lowercase hex characters that identify the answer shape you implemented.

Anything DeploTeka cannot read as that answer, including a timeout or a 5xx, leaves the store Held, not failed. The token is kept, and Send again on the card repeats the delivery once your endpoint works. The only definitive refusal is a non-2xx answer with "status": "rejected" and "reasonCode": "TOKEN_DELIVERY_POLICY_REJECTED".

Examples

Node.js with Express

import express from 'express';
import { createHash } from 'node:crypto';

const app = express();
const TOKEN = process.env.DEPLOTEKA_DELIVERY_TOKEN;
// Any stable sha256 that identifies the ACK shape you implemented.
const ACK_SCHEMA_HASH = 'sha256:' + createHash('sha256').update('token-delivery-ack-v1').digest('hex');

app.post('/api/deploteka/token', express.json(), async (req, res) => {
  if (!TOKEN || req.get('authorization') !== `Bearer ${TOKEN}`) return res.status(401).end();
  const delivery = req.body;
  if (delivery?.contractVersion !== 1) return res.status(400).end();
  // Idempotent on eventId: the same event again returns the same record.
  const { id, replayed } = await saveDelivery(delivery);
  res.json({
    contractVersion: 1,
    eventId: delivery.eventId,
    correlationId: delivery.correlationId,
    status: replayed ? 'replayed' : 'applied',
    receiverOperationRef: id, // 1–128 characters of A-Z a-z 0-9 : _ -
    ackSchemaHash: ACK_SCHEMA_HASH,
  });
});

async function saveDelivery(delivery) {
  // token_issued / token_reissued: store accessToken, apiSecret and clientId for delivery.shop.
  // token_revoked: delete the token you hold for delivery.shop. No accessToken is sent.
}

Python with FastAPI

import hashlib, os
from fastapi import FastAPI, Header, HTTPException, Request

app = FastAPI()
TOKEN = os.environ["DEPLOTEKA_DELIVERY_TOKEN"]
ACK_SCHEMA_HASH = "sha256:" + hashlib.sha256(b"token-delivery-ack-v1").hexdigest()

@app.post("/api/deploteka/token")
async def receive(request: Request, authorization: str = Header("")):
    if authorization != f"Bearer {TOKEN}":
        raise HTTPException(status_code=401)
    delivery = await request.json()
    if delivery.get("contractVersion") != 1:
        raise HTTPException(status_code=400)
    record_id, replayed = save_delivery(delivery)  # idempotent on eventId
    return {
        "contractVersion": 1,
        "eventId": delivery["eventId"],
        "correlationId": delivery["correlationId"],
        "status": "replayed" if replayed else "applied",
        "receiverOperationRef": record_id,
        "ackSchemaHash": ACK_SCHEMA_HASH,
    }

Uninstall

When a merchant uninstalls, you get the same request with event: "token_revoked" and no accessToken. Delete the token you hold for that shop.

Privacy webhooks

Shopify sends every app three privacy webhooks. Your backend holds the merchant’s data, so DeploTeka forwards them to {your address}/api/deploteka/compliance with the same bearer and never follows a redirect. Answer with any 2xx once you have recorded the request.

{
  "contractVersion": 1,
  "event": "compliance_request",
  "topic": "customers/redact",
  "shop": "client-store-shopify-demo.myshopify.com",
  "clientId": "a1b2c3d4e5f6",
  "payload": {
    "…": "Shopify’s own JSON body, unchanged"
  }
}

topic is customers/data_request, customers/redact or shop/redact.