I’m building an Alexa skill using ASK-SDK v.2 for Node.js and having problems connecting to my Airtable database. The skill should let users ask for a hero name and then fetch details from the database.
When I test it in the developer console, even though the slot resolution shows ER_SUCCESS_MATCH, the output always shows the name as UNDEFINED. Alexa responds with “I can’t find UNDEFINED in my database” no matter what name I ask for.
I think the issue is that my skill isn’t actually reaching the Airtable API. I have the right API key and base ID in my code, but I never see the ‘IN AIRTABLE GET’ log message in CloudWatch. The logs jump straight from ‘IN HERO HANDLER’ to ‘END RequestID’.
Here’s my code with sensitive info removed:
'use strict';
const Alexa = require('ask-sdk-core');
const https = require('https');
const SKILL_ID = "amzn1.ask.skill.yyyyyyyyyyyyyyyy";
const StartHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === "LaunchRequest";
},
handle(handlerInput) {
console.log("IN START HANDLER");
return handlerInput.responseBuilder
.speak("Welcome to Hero Info. Ask me about any hero.")
.reprompt("Try asking about a hero.")
.getResponse();
},
};
const HeroHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === "IntentRequest" &&
handlerInput.requestEnvelope.request.intent.name === "HeroIntent";
},
handle(handlerInput) {
console.log("IN HERO HANDLER");
var userInput = extractSpokenValue(handlerInput.requestEnvelope, "heroName");
var matchedValues = extractResolvedValues(handlerInput.requestEnvelope, "heroName");
if (matchedValues === undefined) {
return handlerInput.responseBuilder
.speak("I can't find " + userInput + " in my database.")
.reprompt(userInput + " is not available. Try another hero.")
.getResponse();
}
else if (matchedValues.length === 1) {
var queryFilter = "&filterByFormula=%7BHeroName%7D%3D%22" + encodeURIComponent(matchedValues[0].value.name) + "%22";
return new Promise((resolve) => {
fetchFromAirtable("appYYYYYYYYYY", "Heroes", queryFilter, (result) => {
console.log("AIRTABLE RESULT = " + JSON.stringify(result));
var response = "Here's info about " + userInput + "<break time='.5s'/>" + result.records[0].fields.Description;
resolve(handlerInput.responseBuilder
.speak(response)
.reprompt("Ask about another hero.")
.getResponse());
});
});
}
}
};
function extractSpokenValue(envelope, slotName) {
if (envelope &&
envelope.request &&
envelope.request.intent &&
envelope.request.intent.slots &&
envelope.request.intent.slots[slotName] &&
envelope.request.intent.slots[slotName].value) {
return envelope.request.intent.slots[slotName].value;
}
else return undefined;
}
function extractResolvedValues(envelope, slotName) {
if (envelope &&
envelope.request &&
envelope.request.intent &&
envelope.request.intent.slots &&
envelope.request.intent.slots[slotName] &&
envelope.request.intent.slots[slotName].resolutions &&
envelope.request.intent.slots[slotName].resolutions.resolutionsPerAuthority &&
envelope.request.intent.slots[slotName].resolutions.resolutionsPerAuthority[0] &&
envelope.request.intent.slots[slotName].resolutions.resolutionsPerAuthority[0].values) {
return envelope.request.intent.slots[slotName].resolutions.resolutionsPerAuthority[0].values;
}
else return undefined;
}
function fetchFromAirtable(baseId, tableName, filter, callback) {
console.log("IN AIRTABLE GET");
console.log("BASE ID = " + baseId);
console.log("TABLE = " + tableName);
console.log("FILTER = " + filter);
var requestOptions = {
host: "api.airtable.com",
port: 443,
path: "/v0/" + baseId + "/" + tableName + "?api_key=keyZZZZZZZZZZ" + filter,
method: 'GET',
};
var req = https.request(requestOptions, res => {
res.setEncoding('utf8');
var responseData = "";
res.on('data', chunk => {
responseData = responseData + chunk;
});
res.on('end', () => {
var parsedData = JSON.parse(responseData);
console.log("PARSED DATA = " + JSON.stringify(parsedData));
callback(parsedData);
});
});
req.end();
}
exports.handler = Alexa.SkillBuilders.custom()
.addRequestHandlers(
StartHandler,
HeroHandler
)
.lambda();
Any idea what might be wrong with my Airtable connection?
Your code’s missing error handling on the HTTPS request, so failures happen silently. Also, you’re not waiting for the Airtable response properly - your Promise resolves inside the callback, but there’s no guarantee the async request finishes before Lambda times out.
Add req.on('error', (e) => { console.error('Request error:', e); }); before req.end() to see if the request’s actually failing. Your slot resolution logic looks fine, but log the actual matchedValues array to verify it has what you expect. I’ve seen cases where the resolved value is nested differently than expected, which causes that undefined issue you’re getting.
your request is probably failing silently - add error handling to that https request. also check if your lambda has internet access permissions. sometimes the execution role doesn’t have the right vpc/security group settings to reach external apis like airtable.
You’re building an Alexa skill that needs to fetch data from an Airtable database, but the connection isn’t working correctly. Your skill’s log shows that it’s reaching the HeroHandler, but the IN AIRTABLE GET log message is missing, and Alexa responds with “I can’t find UNDEFINED in my database” regardless of the hero name requested. This indicates the Airtable API call is failing silently.
Understanding the “Why” (The Root Cause):
The core issue is inefficient and error-prone direct API calls from your Alexa skill. Manually handling HTTP requests, promises, error handling, and potential timeouts within the constrained environment of a Lambda function is complex, error-prone, and makes debugging extremely difficult. This approach introduces unnecessary complexity and increases the likelihood of silent failures due to network issues or API rate limits. A more robust, scalable, and maintainable architecture is needed.
Step-by-Step Guide:
Step 1: Migrate to a Webhook-Based Architecture. Instead of making direct API calls from your Alexa skill, implement a webhook system. This separates the data fetching logic from your skill’s core functionality.
Create a separate serverless function (or use a service like Zapier/IFTTT): This function will act as your webhook endpoint. It will be responsible for handling requests from your Alexa skill, fetching data from Airtable, and returning a clean JSON response.
Configure your Alexa skill: Instead of directly calling fetchFromAirtable, your Alexa skill will now send a request to the newly created webhook endpoint with the hero name as a parameter.
Implement the webhook function: This function receives the hero name, fetches the relevant data from Airtable using appropriate Airtable API methods (this will require using the Authorization header correctly, as detailed in other answers, not as query parameters!), formats the data into a clean JSON structure, and sends it back to your Alexa skill. This function should handle errors gracefully (returning appropriate error messages to the Alexa skill) and implement retry logic for transient network issues.
Update your Alexa skill’s HeroHandler: Modify the HeroHandler to send a request to your webhook endpoint using the https module or a similar HTTP client library. Handle the response from the webhook, extracting the hero information and constructing the Alexa response.
Step 2: Implement Robust Error Handling in the Webhook Function. The webhook function should include comprehensive error handling to catch and manage potential problems like:
Airtable API errors: Check for specific HTTP error codes (e.g., 400, 404, 500) and handle them accordingly.
Network errors: Handle timeouts and connection errors gracefully.
Data validation: Ensure the received hero name is valid before querying Airtable.
Step 3: Utilize Airtable’s API Properly. Regardless of your chosen approach (direct API calls or webhook), make sure that your Airtable API calls use the Authorization header correctly to provide authentication. The API key should not be passed as a query parameter.
Common Pitfalls & What to Check Next:
Authentication: Double-check that your Airtable API key has the necessary permissions and that it’s correctly included in the Authorization header (Bearer keyZZZZZZZZZZ).
Rate Limiting: Be aware of Airtable’s API rate limits and implement appropriate retry mechanisms if necessary.
Webhook Endpoint URL: Verify that the URL of your webhook endpoint is correctly configured in your Alexa skill.
JSON Response Formatting: Ensure that the JSON response from your webhook is correctly formatted and contains the information your Alexa skill expects.
Still running into issues? Share your (sanitized) config files, the exact command you ran, and any other relevant details. The community is here to help!
You’re passing the API key as a query parameter, but Airtable wants it in the Authorization header. That’s probably why fetchFromAirtable isn’t working - the request is likely failing without telling you.
Also throw in error handling for your https.request - add an error event listener to catch connection problems. Right now failures just get swallowed and you can’t see what’s breaking. This should fix the API connection and help you debug whatever else comes up.
Had the same issue a few months ago with a different API. Your Promise structure is the problem.
When fetchFromAirtable hits an error (network timeout, bad response, whatever), your Promise never resolves. Lambda just sits there waiting until it times out.
Wrap your entire fetchFromAirtable call in try-catch and add a reject path:
return new Promise((resolve, reject) => {
fetchFromAirtable("appYYYYYYYYYY", "Heroes", queryFilter, (result) => {
if (result && result.records && result.records.length > 0) {
console.log("AIRTABLE RESULT = " + JSON.stringify(result));
var response = "Here's info about " + userInput + "<break time='.5s'/>" + result.records[0].fields.Description;
resolve(handlerInput.responseBuilder.speak(response).reprompt("Ask about another hero.").getResponse());
} else {
resolve(handlerInput.responseBuilder.speak("I can't find " + userInput + " in my database.").getResponse());
}
}, (error) => {
console.error('Airtable error:', error);
resolve(handlerInput.responseBuilder.speak("Sorry, I'm having trouble accessing my database right now.").getResponse());
});
});
Modify fetchFromAirtable to accept an error callback as the fourth parameter and call it when things break.
You’ll actually see what’s going wrong and your skill won’t hang anymore.