code examples
code examples
Developer Guide: Implementing Infobip Delivery Status Callbacks in RedwoodJS
A step-by-step guide on creating a secure RedwoodJS API endpoint to receive, verify, and process Infobip SMS delivery status webhooks.
Track the real-time status of your SMS messages sent via Infobip by implementing webhook callbacks directly within your RedwoodJS application. This guide provides a step-by-step process for creating a secure API endpoint (serverless function) in RedwoodJS to receive, verify, and process delivery status updates pushed from Infobip.
By implementing this, you gain crucial visibility into message delivery, enabling better error handling, analytics, and user experience. You'll know if a message was delivered, failed, or is still pending, allowing your application to react accordingly.
Project Overview and Goals
What We'll Build:
- A RedwoodJS API function (serverless function) that acts as a webhook endpoint.
- Logic within the function to securely verify incoming webhook requests from Infobip.
- A database schema (
MessageLogmodel) using Prisma to store SMS message details and their delivery status. - A RedwoodJS service to update the message status in the database based on the received callback data.
Problem Solved:
This implementation solves the problem of ""fire and forget"" SMS sending. Instead of just sending a message and hoping it arrives, this provides a mechanism to track the delivery lifecycle of each message, enabling:
- Confirmation of successful deliveries.
- Identification of failed messages and reasons for failure.
- Real-time updates for dashboards or user interfaces.
- Automated retries or alternative actions for failed messages.
Technologies Involved:
- RedwoodJS: A full-stack JavaScript/TypeScript framework for the web. We'll use its API side for serverless functions, Prisma integration for the database, and built-in webhook verification tools.
- Infobip: The SMS communications platform. We assume you're already using Infobip to send SMS and will now configure it to push delivery reports back to our application.
- Node.js: The runtime environment for RedwoodJS API functions.
- Prisma: The ORM used by RedwoodJS for database interactions.
- Webhook Verification: Using RedwoodJS's
@redwoodjs/api/webhookspackage for security.
System Architecture:
+-------------+ +-----------------+ +-------------------+ +-----------------+
| User Action | ----> | RedwoodJS App | ----> | Infobip API | ----> | End User |
| (Sends SMS) | | (Calls Send API)| | (Sends SMS) | | (Receives SMS) |
+-------------+ +-----------------+ +-------------------+ +-----------------+
^ |
| (Stores Initial Status) | (Pushes Delivery Report)
| v
+-----------------+ +-----------------+ +-------------------+
| Database | <---- | RedwoodJS App | <---- | Infobip Webhook |
| (MessageLog) | | (Webhook Func) | | |
+-----------------+ +-----------------+ +-------------------+
(Verifies & Updates DB)
Prerequisites:
- Node.js and Yarn (or npm) installed.
- An existing RedwoodJS project (or willingness to create one).
- Basic understanding of RedwoodJS concepts (API functions, services, Prisma).
- An active Infobip account with API access.
- An SMS message already sent via Infobip for which you want to track the status (you'll need its
messageId, which is typically obtained from the response when you initially send the SMS via the Infobip API). - A way to expose your local development environment to the internet (e.g., using
ngrok- to create a public URL for your local machine, as webhooks require a publicly accessible endpoint) for testing webhooks, or deployment to a hosting provider (like Vercel or Netlify) for production.
Final Outcome:
A RedwoodJS application with a secure endpoint /api/infobipWebhook that listens for POST requests from Infobip, verifies their authenticity using a shared secret, parses the delivery status, and updates a corresponding record in the application's database.
1. Setting up the Project Environment
We assume you have an existing RedwoodJS project. If not, create one:
yarn create redwood-app ./my-infobip-app
cd my-infobip-appEnvironment Variables:
Webhooks rely on a shared secret for verification. We'll store this securely in environment variables.
-
Define the Secret: Create a strong, unique secret string. You can use a password generator.
-
Add to
.env: Add the secret to your project's.envfile (create it if it doesn't exist). Never commit.envto version control.bash# .env # Add other variables as needed # Secret used to verify incoming webhooks from Infobip # Ensure this EXACT secret is configured in your Infobip portal INFOBIP_WEBHOOK_SECRET="your_super_secret_string_here_replace_me" -
Accessing Variables: RedwoodJS automatically loads variables from
.envintoprocess.env.
Project Structure:
RedwoodJS provides a clear structure. We'll be working primarily in:
api/src/functions/: Where our webhook handler function will live.api/src/services/: Where our database interaction logic will reside.api/db/schema.prisma: Where we define our database model..env: For environment variables.
No special tooling beyond standard RedwoodJS is needed for this part.
2. Implementing Core Functionality: The Webhook Handler
This function will receive, verify, and process the incoming delivery reports from Infobip.
-
Generate the Function: Use the RedwoodJS CLI to create the serverless function boilerplate.
bashyarn rw g function infobipWebhookThis creates
api/src/functions/infobipWebhook.ts(or.js). -
Implement the Handler: Replace the contents of
api/src/functions/infobipWebhook.tswith the following code:typescript// api/src/functions/infobipWebhook.ts import type { APIGatewayEvent, Context } from 'aws-lambda' import { verifyEvent, VerifyOptions, WebhookVerificationError, } from '@redwoodjs/api/webhooks' import { logger } from 'src/lib/logger' import { db } from 'src/lib/db' // Import Prisma client import { updateMessageStatus } from 'src/services/messageLogs/messageLogs' // Service we'll create later /** * Handles incoming Delivery Report webhooks from Infobip. * Verifies the request signature and updates the message status in the database. */ export const handler = async (event: APIGatewayEvent, _context: Context) => { // Add context for logging, like a correlation ID if available const webhookInfo = { webhook: 'infobipDeliveryReport' } const webhookLogger = logger.child({ webhookInfo }) webhookLogger.info('>> Infobip Webhook Received') webhookLogger.debug({ headers: event.headers }, 'Received headers') // Log raw body *very carefully* only in dev/trace if needed for debugging. // Avoid in production due to potential sensitive data/PII exposure. // webhookLogger.trace({ body: event.body }, 'Received raw body') try { // --- Verification --- // IMPORTANT: Confirm the *exact* header Infobip uses for its signature/secret. // Common patterns include 'Authorization', 'X-Infobip-Signature', etc. // ** CONSULT INFOBIP DOCUMENTATION FOR DELIVERY REPORTS ** const signatureHeader = 'Authorization' // Example: ** MUST BE VERIFIED ** // ** CRITICAL SECURITY POINT ** // Determine the verification method Infobip uses. This example uses // 'secretKeyVerifier' which expects the raw secret directly in the header. // Infobip might use HMAC (e.g., SHA256), which is generally more secure. // If using HMAC, use 'sha256Verifier' or similar and ensure the 'secret' // provided to verifyEvent is the key used for hashing, not the header value itself. // Using the wrong verifier type will lead to failed verification (401). const verifierType = 'secretKeyVerifier' // Example: ** MUST BE VERIFIED ** const options: VerifyOptions = { signatureHeader: signatureHeader, // Additional options might be needed depending on the verifier type // (e.g., 'issuer', 'audience' for JWT, encoding for certain HMACs) } webhookLogger.debug( `Attempting verification using header: ${signatureHeader} and verifier: ${verifierType}` ) // The 'verifyEvent' function checks the signature/secret. // It throws WebhookVerificationError if verification fails. await verifyEvent(verifierType, { event, secret: process.env.INFOBIP_WEBHOOK_SECRET, // Your shared secret options, }) webhookLogger.info('Webhook verified successfully.') // --- Processing --- // Infobip delivery reports usually come as JSON in the body. // ** CONFIRM THE PAYLOAD STRUCTURE FROM INFOBIP DOCUMENTATION ** let payload try { payload = JSON.parse(event.body) webhookLogger.debug({ payload }, 'Parsed webhook payload') } catch (e) { webhookLogger.error({ error: e }, 'Failed to parse request body as JSON.') return { statusCode: 400, body: 'Bad Request: Invalid JSON payload.' } } // Extract necessary data - ** ADJUST PROPERTY NAMES BASED ON ACTUAL INFOBIP PAYLOAD ** // This structure is an EXAMPLE and likely needs adjustment. const results = payload?.results?.[0] // Infobip often nests results in an array if (!results) { webhookLogger.warn('Payload structure unexpected. Missing ""results"" array or object.') return { statusCode: 400, body: 'Bad Request: Unexpected payload structure.' } } // Extract core fields - ** VERIFY THESE FIELD NAMES WITH INFOBIP DOCS ** const messageId = results.messageId const status = results.status?.groupName // e.g., DELIVERED, FAILED, PENDING, REJECTED const statusDescription = results.status?.description const errorCode = results.error?.id const errorDescription = results.error?.description if (!messageId || !status) { webhookLogger.warn('Missing required fields (messageId, status groupName) in payload results.') return { statusCode: 400, body: 'Bad Request: Missing required fields.' } } // --- Update Database --- await updateMessageStatus({ messageId, status, statusDescription, errorCode, errorDescription, }) webhookLogger.info(`Successfully processed status update for messageId: ${messageId}`) // --- Respond to Infobip --- // Acknowledge receipt successfully. Infobip typically expects a 2xx status. return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: 'Webhook processed successfully.' }), } } catch (error) { if (error instanceof WebhookVerificationError) { webhookLogger.warn({ error }, 'Webhook verification failed.') return { statusCode: 401, body: 'Unauthorized' } // Verification failure } else { webhookLogger.error({ error }, 'Error processing Infobip webhook.') // Avoid exposing internal error details to the caller return { statusCode: 500, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ error: 'Internal Server Error' }), } } } }
Explanation:
- Imports: Standard imports for AWS Lambda types, RedwoodJS webhook utils, logger, DB client, and the service function.
- Logging: Contextual logger for traceability. Added stronger warning about logging raw body in production.
- Verification:
signatureHeader: Crucially important: You must confirm the exact HTTP header Infobip uses (e.g.,Authorization,X-Infobip-Signature).verifierType: Equally critical: Determine if Infobip uses a simple secret ('secretKeyVerifier') or a more secure HMAC method (like'sha256Verifier'). Using HMAC is generally recommended if available.VerifyOptions: Configures the verification based on the header.verifyEvent: Executes the verification using the chosen type, event data, your secret, and options. ThrowsWebhookVerificationErroron failure.
- Processing:
- Parses the
event.bodyas JSON. Confirm the expected Content-Type and structure. - Extracts fields (
messageId,status, etc.). The structure (payload.results[0]...) is an illustrative example. You must inspect actual Infobip payloads or documentation. ThemessageIdneeded for database lookup is extracted here from the body.
- Parses the
- Database Update: Calls the
updateMessageStatusservice. - Response: Returns
200 OKon success. - Error Handling: Catches
WebhookVerificationError(returns401) and other errors (returns500, logs details internally).
3. Building the API Layer
The RedwoodJS function (/api/infobipWebhook) is our API endpoint.
- Endpoint:
POST /api/infobipWebhook(Path might vary slightly based on deployment, e.g., Netlify uses/.netlify/functions/infobipWebhook). - Method:
POST - Authentication: Handled by webhook signature verification (
verifyEvent). - Request Validation: Basic payload parsing and field presence checks.
- Request Body: Expected to be JSON (confirm with Infobip). The structure must be verified.
- Response Body:
200 OK:{ "message": "Webhook processed successfully." }400 Bad Request: Error message for invalid payload/missing fields.401 Unauthorized: Signature verification failed.500 Internal Server Error:{ "error": "Internal Server Error" }
Testing with curl (requires ngrok or deployment):
This example assumes your webhook is at https://your-ngrok-id.ngrok.io/api/infobipWebhook, uses secretKeyVerifier, expects the secret your_super_secret_string_here_replace_me in the Authorization header, and receives the example JSON payload structure below. You MUST adjust the URL, header, secret, and payload structure based on your actual setup and Infobip's specifications.
# Example Payload - ILLUSTRATIVE ONLY - ADJUST TO MATCH ACTUAL INFOBIP FORMAT
JSON_PAYLOAD='{
"results": [
{
"messageId": "some-infobip-message-id-123",
"to": "15551234567",
"status": {
"groupId": 1,
"groupName": "DELIVERED",
"id": 5,
"name": "DELIVERED_TO_HANDSET",
"description": "Message delivered to handset"
},
"error": {
"groupId": 0,
"groupName": "OK",
"id": 0,
"name": "NO_ERROR",
"description": "No Error",
"permanent": false
},
"sentAt": "2024-01-10T10:00:00.000+0000",
"doneAt": "2024-01-10T10:00:05.000+0000"
}
]
}'
# Test Success
curl -X POST \
https://your-ngrok-id.ngrok.io/api/infobipWebhook \
-H "Content-Type: application/json" \
-H "Authorization: your_super_secret_string_here_replace_me" \
-d "$JSON_PAYLOAD"
# Expected Success Response (200 OK):
# {"message":"Webhook processed successfully."}
# Test Failure (Incorrect Secret)
curl -X POST \
https://your-ngrok-id.ngrok.io/api/infobipWebhook \
-H "Content-Type: application/json" \
-H "Authorization: wrong_secret" \
-d "$JSON_PAYLOAD"
# Expected Failure Response (401 Unauthorized):
# Unauthorized4. Integrating with Infobip
Configure Infobip to send delivery reports to your RedwoodJS webhook endpoint.
-
Find Webhook Configuration in Infobip: Log in to your Infobip account. Look for settings related to:
- API Settings
- Webhook Configuration
- Delivery Reports (DLR)
- SMS Product Settings -> Callbacks or Webhooks (The exact location may vary, consult Infobip support or documentation if needed.)
-
Configure the Endpoint URL:
- Enter the full, publicly accessible URL of your deployed RedwoodJS function (e.g.,
https://<your-app-domain>.com/.netlify/functions/infobipWebhookorhttps://<your-app-domain>.com/api/infobipWebhook). - For local testing, use the
https://URL provided byngrok.
- Enter the full, publicly accessible URL of your deployed RedwoodJS function (e.g.,
-
Configure Authentication/Security:
- Find the security settings for the webhook.
- Method: Select the method matching your implementation (e.g., ""HTTP Header Authentication"" if using
secretKeyVerifierwithAuthorization, or ""HMAC"" if using an HMAC verifier). This must match your code'sverifierTypeandsignatureHeader. - Secret/Token/Key: Enter the exact same secret string from your
.envfile (INFOBIP_WEBHOOK_SECRET). - Header Name: If applicable (e.g., for simple header auth), specify the header name used in
VerifyOptions(e.g.,Authorization). If using HMAC, Infobip often dictates the header (e.g.,X-Infobip-Signature) - ensure this matches yoursignatureHeadersetting.
-
Enable Delivery Reports: Ensure Delivery Reports are enabled for the SMS sending method you are using (e.g., via API).
Environment Variables Recap:
INFOBIP_WEBHOOK_SECRET:- Purpose: Shared secret for authenticating/verifying webhook requests.
- Format: Strong, unique string.
- How to Obtain: Generate it yourself. Ensure the same value is in your application's environment and the Infobip portal configuration.
5. Error Handling, Logging, and Retry Mechanisms
- Error Handling Strategy:
WebhookVerificationError->401 Unauthorized.- Payload parsing errors / missing required fields ->
400 Bad Request. - Other internal errors (database, etc.) ->
500 Internal Server Error.
- Logging: Uses RedwoodJS
logger. ConfigureLOG_LEVELappropriately for dev vs. prod. Logs go to console/hosting provider logs. - Retry Mechanisms (Infobip Side): Infobip typically retries if your endpoint fails to return a
2xxstatus quickly.- Responses like
401or400usually signal a permanent issue with the request itself, often preventing retries. (This is typical webhook behavior, but confirm Infobip's specific retry logic in their documentation.) 500errors or timeouts likely trigger retries. Design yourupdateMessageStatuslogic to be idempotent (safe to run multiple times with the same input) usingupsertor checks.
- Responses like
6. Creating Database Schema and Data Layer
Store message status information.
-
Define Prisma Schema: Add/update a model in
api/db/schema.prisma.prisma// api/db/schema.prisma datasource db { provider = ""postgresql"" // Or your chosen database url = env(""DATABASE_URL"") } generator client { provider = ""prisma-client-js"" } // Stores logs for messages sent via Infobip model MessageLog { id String @id @default(cuid()) messageId String @unique // The ID received from Infobip when sending the SMS status String? // Status from webhook (e.g., PENDING, DELIVERED, FAILED) statusDescription String? // Detailed description from Infobip errorCode Int? // Infobip error code if applicable errorDescription String? // Infobip error description createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Automatically updated by Prisma // Consider adding fields populated when the message is initially sent: // to String? // from String? // body String? // Be mindful of PII // initialSentAt DateTime? }Recommendation: Your application logic that sends the SMS should ideally create an initial
MessageLogrecord with themessageIdand a status likePENDINGorSENT. -
Create Migration:
bashyarn rw prisma migrate dev --name add_message_log -
Create Service:
bashyarn rw g service messageLogs -
Implement Update Logic: Add
updateMessageStatustoapi/src/services/messageLogs/messageLogs.ts.typescript// api/src/services/messageLogs/messageLogs.ts import type { Prisma } from '@prisma/client' import { db } from 'src/lib/db' import { logger } from 'src/lib/logger' interface UpdateMessageStatusArgs { messageId: string status: string statusDescription?: string | null // Allow null from payload errorCode?: number | null errorDescription?: string | null } export const updateMessageStatus = async ({ messageId, status, statusDescription, errorCode, errorDescription, }: UpdateMessageStatusArgs) => { logger.debug( `Updating status for messageId ${messageId} to ${status}` ) try { // Use upsert for robustness: Update if exists, create if not. // This handles cases where the initial send might not have created a log, // although ideally, it should. If creation is guaranteed elsewhere, // `update` can be used instead. const messageLog = await db.messageLog.upsert({ where: { messageId: messageId }, update: { status: status, statusDescription: statusDescription, errorCode: errorCode, errorDescription: errorDescription, // updatedAt is handled automatically }, create: { messageId: messageId, status: status, statusDescription: statusDescription, errorCode: errorCode, errorDescription: errorDescription, // Add initial values for other fields if creating and applicable }, }) logger.info(`Database updated for messageId: ${messageId}`) return messageLog } catch (error) { logger.error( { error, messageId }, `Failed to update message status in DB for messageId: ${messageId}` ) // Re-throw to signal failure to the webhook handler throw error } } // Optional: Add standard service functions if needed for other operations // export const messageLogs = () => { // return db.messageLog.findMany() // } // // export const messageLog = ({ id }: Prisma.MessageLogWhereUniqueInput) => { // return db.messageLog.findUnique({ // where: { id }, // }) // }Explanation:
- Uses
db.messageLog.upsertbased on the uniquemessageId. - Updates status fields if the record exists.
- Creates a new record if it doesn't exist (though creating during the initial send is preferred).
- Logs errors and re-throws them.
- Uses
7. Adding Security Features
- Webhook Signature Verification: Mandatory. Implemented via
@redwoodjs/api/webhooks. Ensures authenticity and integrity. - HTTPS: Mandatory. Encrypts data in transit. Use
https://URLs. Standard on deployment platforms andngrok. - Input Validation: Basic checks in the handler prevent processing malformed data.
- Secret Management: Use environment variables (
.env, platform settings) forINFOBIP_WEBHOOK_SECRET. Never hardcode secrets. Consider secret rotation policies. - Rate Limiting: (Optional) Implement at the infrastructure level (API Gateway, platform features) if expecting very high webhook volume.
8. Handling Special Cases
- Infobip Status Codes/Groups: Understand Infobip's specific status values (
DELIVERED,UNDELIVERABLE,FAILED, etc.) and error codes to build appropriate application logic. - Payload Structure Variations: Be prepared for potential differences in payload structure. Test thoroughly and consult Infobip docs.
- Idempotency: Ensure
updateMessageStatusis safe to re-run with the same data (achieved viaupsertor status checks) due to potential Infobip retries. - Time Zones: Be mindful of timezone information in timestamps (
sentAt,doneAt) provided by Infobip.
9. Implementing Performance Optimizations
Generally not needed unless facing very high volume:
- Database Indexing: Prisma automatically creates a unique index on
messageId(@unique), which is essential forupsert/updateperformance. - Asynchronous Processing: For slow downstream tasks triggered by the webhook, offload work to a background job queue (e.g., RedwoodJS Background Jobs) after verification. The handler returns
200 OKquickly. - Connection Pooling: Handled by Prisma. Ensure database resources are adequate.
10. Adding Monitoring, Observability, and Analytics
- Logging: Use platform logging tools (Netlify/Vercel logs, etc.) to monitor function execution and errors.
- Health Checks: Endpoint responding
200 OKis a basic health indicator. - Error Tracking: Integrate services like Sentry or Bugsnag (see RedwoodJS docs/recipes).
- Metrics & Dashboards: Track webhook counts (total, 2xx, 4xx, 5xx), function latency, and message status distribution (e.g.,
DELIVEREDvs.FAILED) using platform metrics or database queries. - Alerting: Set up alerts for high error rates (
401,500), increasedFAILEDstatuses, or function timeouts.
11. Troubleshooting and Caveats
- Verification Failed (
401 Unauthorized):- Check
INFOBIP_WEBHOOK_SECRET(exact match in env vs. Infobip portal). - Check
signatureHeaderin code vs. what Infobip sends. - Check
verifierTypein code vs. Infobip's configured method (e.g.,secretKeyVerifiervs. HMAC/sha256Verifier). - For HMAC, ensure request body isn't modified before verification.
- Check
- Infobip Not Sending Webhooks:
- Check Endpoint URL in Infobip (correct, public, HTTPS).
- Check Delivery Reports are enabled in Infobip.
- Check Infobip's webhook delivery logs for errors.
- Payload Parsing Error (
400 Bad Request/ Log Error):- Verify expected
Content-Type(application/json?). - Inspect raw
event.body(in dev) to confirm structure; adjust parsing logic (payload.results[0]...) accordingly.
- Verify expected
- Database Errors (
500 Internal Server Error/ Log Error):- Check
DATABASE_URLand connectivity. - Verify Prisma schema matches data.
- Check database constraints (e.g.,
messageIduniqueness).
- Check
- Platform Limitations: Be aware of serverless function execution time limits. Use background jobs for long tasks.
- Infobip Documentation is Key: Details like header names, payload structure, and authentication methods must be confirmed with the official Infobip developer documentation for Delivery Reports. Do not rely solely on the examples here.
12. Deployment and CI/CD
- Choose Deployment Provider: Vercel, Netlify, Render, etc.
- Configure Environment Variables: Set
INFOBIP_WEBHOOK_SECRET,DATABASE_URLin the provider's UI for production. - Deploy: Use
yarn rw deploy <provider>. - CI/CD: Usually set up automatically by the provider based on Git pushes.
- Database Migrations: Run production migrations (
yarn rw prisma migrate deploy) as part of your deployment process. - Rollback: Use provider features to revert to previous deployments if needed.
13. Verification and Testing
-
Manual Verification:
- Deploy or use
ngrok. - Configure Infobip webhook URL and secret.
- Send an SMS via Infobip, note
messageId. - (Recommended) Ensure an initial
MessageLogrecord exists (statusPENDING). - Wait for the webhook.
- Check application logs for successful receipt, verification, and processing messages. No errors.
- Check the database:
MessageLogrecord for themessageIdshould be updated correctly.
- Deploy or use
-
Automated Testing (Service Layer):
- Focus testing on the
messageLogsservice (updateMessageStatus) using RedwoodJS scenarios to ensure database logic is correct. Mocking signed webhooks directly is complex. - Example Service Test (
api/src/services/messageLogs/messageLogs.test.ts):
typescript// api/src/services/messageLogs/messageLogs.test.ts import { updateMessageStatus } from './messageLogs' import { db } from 'src/lib/db' // Uses test database context provided by scenarios scenario('MessageLogs service', 'updates an existing message log status', async (scenario) => { // Assumes a scenario named 'messageLog' defines a basic log record const existingLog = scenario.messageLog.one const messageId = existingLog.messageId const updatedLog = await updateMessageStatus({ messageId: messageId, status: 'DELIVERED', statusDescription: 'Delivered to handset', }) expect(updatedLog.messageId).toEqual(messageId) expect(updatedLog.status).toEqual('DELIVERED') expect(updatedLog.statusDescription).toEqual('Delivered to handset') const dbRecord = await db.messageLog.findUnique({ where: { messageId }}) expect(dbRecord.status).toEqual('DELIVERED') }) scenario('MessageLogs service', 'creates a message log if it does not exist (upsert)', async () => { const messageId = 'new-message-id-upsert-test' // No initial record for this messageId const newLog = await updateMessageStatus({ messageId: messageId, status: 'FAILED', errorCode: 2, errorDescription: 'Absent Subscriber' }) expect(newLog.messageId).toEqual(messageId) expect(newLog.status).toEqual('FAILED') const dbRecord = await db.messageLog.findUnique({ where: { messageId }}) expect(dbRecord).not.toBeNull() expect(dbRecord.status).toEqual('FAILED') }) - Focus testing on the
-
Verification Checklist:
-
INFOBIP_WEBHOOK_SECRETmatches in env/platform and Infobip portal. - Correct Endpoint URL configured in Infobip.
- Correct verification method (
verifierType) andsignatureHeaderused in code match Infobip config. - Webhook function deployed and publicly accessible (HTTPS).
- Test SMS sent.
- Infobip logs show successful delivery attempt (2xx response).
- Application logs show successful verification & processing.
-
MessageLogtable updated correctly. - Tested failure case (e.g., wrong secret ->
401logged).
-
14. GitHub Repository
A complete working example including the setup described in this guide can be found here:
(Link to GitHub Repository to be added)
This guide provides a robust foundation for handling Infobip delivery reports in your RedwoodJS application. Remember to always consult the official Infobip documentation for the definitive details regarding their payload structure, signature methods, header names, and status codes, as these are critical for a secure and functional implementation.
Frequently Asked Questions
How to track Infobip SMS delivery status in RedwoodJS?
Implement a webhook callback function in your RedwoodJS API. This function will receive real-time delivery updates from Infobip, allowing you to monitor message status, handle errors, and improve user experience by providing feedback or taking alternative actions.
What is the purpose of Infobip delivery report webhooks?
Infobip webhooks provide real-time updates on the status of your sent SMS messages. This moves beyond "fire and forget" messaging, giving you insights into delivery success, failures, and pending statuses, so your application can react accordingly.
Why does RedwoodJS need webhook verification for Infobip?
Webhook verification is crucial for security. It confirms that incoming requests genuinely originate from Infobip, preventing unauthorized access or malicious data manipulation by verifying signatures using a shared secret.
When should I create the MessageLog record in Prisma?
Ideally, create the `MessageLog` record with a status like `PENDING` when the SMS is initially sent via the Infobip API. The webhook handler then updates this record based on the delivery report. The handler uses `upsert` to create a record if one doesn't already exist, but it's best practice to create initially.
What is the RedwoodJS API endpoint for Infobip webhooks?
The endpoint is typically `/api/infobipWebhook` for a RedwoodJS function named `infobipWebhook`. This path might vary slightly depending on the deployment platform (e.g., Netlify uses `/.netlify/functions/infobipWebhook`), but it's always a POST request.
How to verify Infobip webhook requests in RedwoodJS?
Use the `verifyEvent` function from `@redwoodjs/api/webhooks`. Provide the correct `verifierType` ('secretKeyVerifier' or an HMAC verifier), the shared secret from your `.env` file, and specify the `signatureHeader` used by Infobip.
How to set up Infobip to send delivery reports to my app?
Log into your Infobip account and navigate to the Webhook or Callback settings (often under API or DLR settings). Configure the URL of your RedwoodJS endpoint (public and HTTPS), and set the authentication method (e.g., HTTP Header Authentication or HMAC) and secret, ensuring these match your application's setup.
How to handle webhook verification failures with Infobip?
Verification failures (401 Unauthorized errors) usually indicate an issue with the shared secret or the HTTP headers used for verification. Double-check that the `INFOBIP_WEBHOOK_SECRET` in your app's environment precisely matches the one configured in your Infobip account, and that the header names (`signatureHeader`) align.
What are common troubleshooting steps for Infobip webhooks?
If Infobip webhooks are not working, verify the following: correct endpoint URL, delivery reports enabled in Infobip, proper setup of webhook security (secret, signature method), and log files from both Infobip and your application for clues about the issue. Check Infobip's documentation to confirm their webhook configuration options and payload format.
How to store Infobip delivery status in a database?
Define a `MessageLog` model in your Prisma schema with fields for `messageId`, `status`, error codes, descriptions, timestamps, etc. Create a RedwoodJS service with an `updateMessageStatus` function that uses Prisma to upsert records based on the `messageId` received in the webhook payload.
How to test my Infobip webhook integration?
Deploy your application or use ngrok to expose it publicly. Send test SMS messages through Infobip and observe the logs to verify the webhook function is correctly receiving, verifying, and processing data. Confirm that the database is updating correctly and that your error handling is working.
Can I use ngrok for testing Infobip webhooks locally?
Yes, ngrok creates a public HTTPS URL for your local development environment, enabling you to test webhooks from Infobip during development before deploying your application.
Why does my Infobip webhook handler return a 400 Bad Request?
A 400 Bad Request error usually indicates a problem parsing the incoming JSON payload from Infobip. Double-check Infobip's documentation for the expected structure and ensure your parsing logic extracts data correctly. Log the raw body carefully (in development) to understand the payload format.
How to secure Infobip webhooks in RedwoodJS?
Use webhook signature verification (`@redwoodjs/api/webhooks`), always use HTTPS, validate incoming payload structure, and securely store the shared secret (`INFOBIP_WEBHOOK_SECRET`) in environment variables, never hardcoding it. Rate limiting can be an additional layer of security, preventing abuse.