Skip to main content

Push Hooks

Alpha

This feature is in alpha. APIs and behavior may change without notice. Not recommended for production use.

Push hooks let you intercept outbound push sends before delivery or observe the delivery result after the send completes.

They are defined under push.handlers.hooks in edgebase.config.ts.

Overview

HookTimingBehaviorCan ModifyCan Reject
beforeSendBefore EdgeBase sends to FCMBlockingYes (return new input)Yes (throw)
afterSendAfter EdgeBase receives the provider resultNon-blocking (waitUntil)NoNo

Push hooks run only for server-side push sends such as:

  • admin.push.send(...)
  • admin.push.sendMany(...)
  • admin.push.sendToToken(...)
  • admin.push.sendToTopic(...)
  • admin.push.broadcast(...)

Client token registration and unregistration do not trigger push hooks.

Configuration

import { defineConfig } from '@edgebase/shared';

export default defineConfig({
push: {
access: {
send(auth) {
return auth !== null;
},
},
handlers: {
hooks: {
beforeSend: async (_auth, input) => {
return {
...input,
payload: {
...input.payload,
sentAt: new Date().toISOString(),
},
};
},
afterSend: async (_auth, input, output, ctx) => {
ctx.waitUntil(
fetch('https://audit.example.com/push-log', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
kind: input.kind,
sent: output.sent,
failed: output.failed,
removed: output.removed,
}),
}).catch(() => {}),
);
},
},
},
},
});

beforeSend

Runs before EdgeBase dispatches the push request. Return a modified PushSendInput to rewrite the outbound target or payload, or throw to reject the send.

beforeSend: async (_auth, input) => {
if (input.kind === 'topic') {
return {
...input,
topic: `prod-${input.topic}`,
};
}

return {
...input,
payload: {
...input.payload,
body: `[EdgeBase] ${String(input.payload.body ?? '')}`,
},
};
},

Input Shape

interface PushSendInput {
kind: 'user' | 'users' | 'token' | 'topic' | 'broadcast';
payload: Record<string, unknown>;
userId?: string;
userIds?: string[];
token?: string;
topic?: string;
platform?: string;
}

If beforeSend returns an invalid structure for the selected kind, the server rejects the request with 400.

afterSend

Runs after the provider call completes. This hook is best-effort and does not change the response already returned to the caller.

afterSend: async (_auth, input, output, ctx) => {
ctx.waitUntil(
Promise.resolve().then(() => {
console.log('Push send finished', input.kind, output.sent, output.failed);
}),
);
},

Output Shape

interface PushSendOutput {
sent?: number;
failed?: number;
removed?: number;
error?: string;
raw?: unknown;
}

Hook Context

interface PushHookCtx {
request?: Request;
waitUntil(promise: Promise<unknown>): void;
}

Common Uses

  • Prefix topic names per environment
  • Add standard metadata to all payloads
  • Enforce last-minute send policies beyond push.access.send
  • Record provider results in an audit log
  • Trigger follow-up work when tokens are removed or delivery fails

See Also