Skip to content

Instantly share code, notes, and snippets.

@marvinkome
Created August 29, 2025 08:42
Show Gist options
  • Select an option

  • Save marvinkome/4bb156219051cbd5156d58fd68597e8b to your computer and use it in GitHub Desktop.

Select an option

Save marvinkome/4bb156219051cbd5156d58fd68597e8b to your computer and use it in GitHub Desktop.
import fs from "fs/promises";
import path from "path";
import { env } from "@/libs/env/server";
import { AppStoreServerAPIClient, Environment, SignedDataVerifier } from "@apple/app-store-server-library";
const loadRootCertificate = async () => {
const certificatesDirectory = path.join(process.cwd(), "./apple-root-certificates");
return Promise.all([
fs.readFile(path.join(certificatesDirectory, "./cert-1.cer")),
fs.readFile(path.join(certificatesDirectory, "./cert-2.cer")),
fs.readFile(path.join(certificatesDirectory, "./cert-3.cer")),
fs.readFile(path.join(certificatesDirectory, "./cert-4.cer")),
]);
};
export const getClient = async () => {
const environment = process.env.NODE_ENV === "production" ? Environment.PRODUCTION : Environment.SANDBOX;
const apiKey = env.APP_STORE_PRIVATE_KEY.replace(/\\n/g, "\n");
const client = new AppStoreServerAPIClient(apiKey, env.APP_STORE_KEY_ID, env.APP_STORE_ISSUER_ID, env.APP_STORE_BUNDLE_ID, environment);
const certificates = await loadRootCertificate();
const verifier = new SignedDataVerifier(certificates, true, environment, env.APP_STORE_BUNDLE_ID, env.APP_STORE_APP_ID);
return { client, verifier };
};
export async function POST(request: NextRequest) {
const { signedPayload } = await request.json();
console.log("[apple-webhook] received signed notification from apple");
const { verifier } = await getClient();
try {
const payload = await verifier.verifyAndDecodeNotification(signedPayload);
console.log("[apple-webhook] decoded signed payload", { payload: _.omit(payload, ["data"]) });
await handleNotification(payload);
return NextResponse.json({ message: "handled" });
} catch (error: any) {
console.error("[apple-webhook] error decoding signed payload - %j", {
name: error.name,
message: error.message,
stack: error.stack,
});
return NextResponse.json({ error: "request failed" }, { status: 400 });
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment