Good morning, I am configuring in backend the sending of reports regarding purchases made in app with external platform (Stripe) as per documentation. To be clear I am talking about ExternalPurchase. However, when I make the call it returns "Apple responded with status 401". I verified the token on jwt.io as per documentation and it is working. I don't understand where I am going wrong. Below I share the code with you:
const express = require("express");
const bodyParser = require("body-parser");
const jwt = require("jsonwebtoken");
const fs = require("fs");
const app = express();
const https = require("https");
const APPLE_KEY_ID = "XXXXX";
const APPLE_ISSUER_ID = "***-***-***-xx-xxxxxx";
const APPLE_PRIVATE_KEY = fs.readFileSync("AuthKey_xxxxx.p8", "utf8");
const APPLE_AUDIENCE = "appstoreconnect-v1";
function generateAppleJwt() {
const now = Math.floor(Date.now() / 1000);
const payload = {
iss: APPLE_ISSUER_ID,
iat: now,
exp: now + (5 * 60),
aud: APPLE_AUDIENCE
};
return jwt.sign(payload, APPLE_PRIVATE_KEY, {
algorithm: "ES256",
header: {
alg: "ES256",
kid: APPLE_KEY_ID,
typ: "JWT"
}
});
}
app.post('/webhook', bodyParser.json({ type: 'application/json' }), async (req, res) => {
let eventType = req.body.type;
const relevantEvents = [
"invoice.paid"
];
if (relevantEvents.includes(eventType)) {
try {
const data= req.body.data;
const platform = data.object.subscription_details.metadata.platform;
if (platform === "IOS") {
const token = generateAppleJwt();
const applePayload = {
appAppleId: "xxxx",
bundleId: 'com.***.***.test',
externalPurchaseId: data.object.id,
purchaseTime: new Date(data.object.created * 1000).toISOString(),
purchaseAmount: {
amount: (data.object.total / 100).toFixed(2),
currencyCode: data.object.currency.toUpperCase()
},
purchaseLocation: {
isoCountryCode: "IT"
}
};
const jsonString = JSON.stringify(applePayload);
const agent = new https.Agent({ keepAlive: false });
const response = await fetch(
"https://api.storekit-sandbox.apple.com/externalPurchase/v1/reports",
{
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
"Accept-Encoding": "identity",
},
body: JSON.stringify(applePayload),
}
);
if (!response.ok) {
const errorText = await response.text();
throw new Error(
`Apple responded with status ${response.status}: ${errorText}`
);
}
console.log("✅ Notifica inviata ad Apple con successo");
} else {
if(!canSendNotification){
console.log("Non è una Sub. Nessuna notifica inviata.");
}else{
console.log("Customer non iOS. Nessuna notifica inviata.");
}
}
} catch (err) {
console.error("Errore durante l’invio ad Apple:");
if (err.response) {
console.error("Status:", err.response.status);
console.error("Headers:", err.response.headers);
console.error("Data:", err.response.data);
} else {
console.error("Message:", err.message);
}
}
}
res.status(200).send("OK");
});
exports.checkSubStripe = functions.https.onRequest(app);