Watson Assistant only supports one Webhook url. The issue is I have multiple nodes in Assistant that need to call different "Actions" I have hosted in IBM Cloud Functions. How can I do that? I tried to code and "Action" that uses the NPM Axios to call the other "Actions" but it fails as it lacks authentication.
Can someone tell me how to do it? I need the BASE URL to be athenticated via my IBM Cloud user API to grant access to my namespace.
this is the action I created that acts as a "Dispatch" for webhooks for Watson Assistant.
/**
*
* main() will be run when you invoke this action
*
* #param Cloud Functions actions accept a single parameter, which must be a JSON object.
*
* #return The output of this action, which must be a JSON object.
*
*
* Version: 1.5 beta (API HUB)
* Date: 24/04/2020
*
*/
const axios = require("axios");
// Change to your "BASE_URL". Type in the "Web Action" url without the name of it at the end. (must NOT contains the end '/')
// All "Actions" must be within the same namespace.
const BASE_URL = "https://jp-tok.functions.cloud.ibm.com/api/v1/web/
const POST = "post";
const GET = "get";
const ANY = "any";
const ALL = "all";
/* List all your API methods
1. method - API Name (This is the "Actions" name at the end of the Web Action URL or just the name)
2. attr - Attributes that should be available in the params object
3. rule - Currently supports 2 rules;
a) any - params is valid if "any" of the attributes are present
b) all - params is valid only if all attributes are present
4. httpmethod -Supports "POST" and "GET"
5. contains - Use for validating GET URL parameters
*/
const API_METHODS = [{
method: "Emails", // Change to your API method "Please put the function name located at the end of the url without "/" example "Email"
attr: ["client_email", "department_email"],
rule: ANY,
httpmethod: POST,
contains: null
},
{
method: "testapi", // If Watson needs to "GET" information to a user or athenticate a user
attr: [],
rule: ALL,
httpmethod: GET,
contains: "?ID="
},
]
// Returns true if the "params" is valid for the given API method
function isValidParam(method, params = {}) {
var attributes = [];
attributes = method.attr;
var isAny = method.rule === ANY;
for (let index = 0; index < attributes.length; index++) {
const attr = attributes[index];
if (isAny) {
if (Object.hasOwnProperty.call(params, attr)) {
return true;
}
} else {
if (!Object.hasOwnProperty.call(params, attr)) {
return false;
}
}
}
if (attributes.length === 0 && method.contains) {
return (JSON.stringify(params).indexOf(method.contains) > -1)
}
// if the code reaches this position, inverse of "isAny" should return the actual validation status.
return !isAny;
}
async function main(params) {
var result = [];
// Stop on first failure
// We iterate through all API methods. Because there can be more than one matching API for the given param type
for (let index = 0; index < API_METHODS.length; index++) {
const apiMethod = API_METHODS[index];
const url = BASE_URL + '/' + apiMethod.method;
if (isValidParam(apiMethod, params)) {
let response = apiMethod.httpmethod === POST ?
await axios.post(url, params) :
await axios.get(url + params); // Expects the parameter to be a string in this case like '?id=345343'
console.log(response);
result.push({
sent: true,
url: url,
request: params,
});
}
}
return {
sent: true,
details: result
};
}
// THe part of the code that needs to be copied to call other functions within the namespace.
/* const API_METHODS = [{
method: "Emails", // Change to your API method "Please put the function name located at the end of the url without "/" example "Email"
* attr: ["bamboo_email", "latitude_email", "latest_r_email", "department_email"],
rule: ANY,
httpmethod: POST,
contains: null
},
{
method: "testapi", // If Watson needs to "GET" information to a user or athenticate a user
attr: [],
rule: ALL,
httpmethod: GET,
contains: "?ID="
},
] */
When I run it in Watson Assistant I get the following error:
Webhook call response body exceeded [1050000] byte limit.response code: 200, request duration: 591, workspace_id: 150088ee-4e86-48f5-afd5-5b4e99d171f8, transaction_id: a8fca023d456d1113e05aaf1f59b1b2b, url_called: https://watson-url-fetch.watson-url-fetcher:443/post?url=https%3A%2F%2Fjp-tok.functions.cloud.ibm.com%2Fapi%2Fv1%2Fweb%2F23d61581-fa68-4979-afa2-0216c17c1b29%2FWatson+Assistant%2FAssistant+Dispatch.json (and there is 1 more error in the log)
Related
I'm facing an issue with the Binance API
Whenever I try to make a request for withdrawal, it returns "Signature for this request is not valid." error message, even though if with the same function I make a request for the account info, Funding balance, getAll balances or whatever, it works.
I already tried several solutions, but nothing seemed to work. This are some of my tries reading similar issues
Works with endpoints like "api/v3/account" or "sapi/v1/capital/config/getall"
Sending the signature and the params in the body (it returns "You are not authorized to execute this request" error message, I think is because is not reading the body, only query params)
Activate all permissions for the API, and check the API
Send in the query the param "name"
Using Node packages like node-binance-api (It's not up to date with the request for withdrawals)
Changing scripts in librery node-binance-api to the actual api for withdrawals (same error as doing it myself)
I'm using NodeJs, axios to make the requests. This is my function:
/**
* Send Request to withdraw USDT funds from the selected wallet to a specified address
* #param wallet
* #param amount
* #param address
* #returns
* #link https://binance-docs.github.io/apidocs/spot/en/#withdraw-user_data
*/
makeWithdrawal(
wallet: WalletType = 'SPOT',
amount: number,
address: string
) {
const walletIndex = WalletEnum[wallet];
return this.makeRequest<{ id: string }>(
'sapi/v1/capital/withdraw/apply',
'POST',
{
coin: 'USDT',
amount,
wallet: walletIndex,
address,
network: 'TRX',
name: '_'
}
);
}
/**
* Makes Request to Binance Pay API
* #param endpoint
* #param method
* #param params
* #returns
* #link https://binance-docs.github.io/apidocs/spot/en
*/
private makeRequest<T>(
endpoint: string,
method: Method = 'GET',
params: {[key: string]: string | number} = {}
): Promise<T> {
const timestamp = Number(new Date().getTime()).toFixed(0);
let query = `timestamp=${timestamp}`;
for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
const value = params[key];
query += `&${key}=${value}`
}
}
const signature = this.sign(query);
const headers: AxiosRequestHeaders = {
'X-MBX-APIKEY': process.env.BINANCE_API_KEY,
};
const requestObservable = this.httpService.request<T>({
method,
url: `${process.env.BINANCE_API_URL}/${endpoint}`,
params: {...params, timestamp, signature},
headers
}).pipe(
map(res => res.data)
);
return lastValueFrom(requestObservable);
}
/**
* Signs payload with private key
* #param payload
* #returns
*/
private sign(query: string) {
return createHmac('sha256', process.env.BINANCE_API_SECRET)
.update(query)
.digest('hex');
}
I really can't understand what is going, if someone could please help me to solve this, as I'm reading this is a common issue, but I really tried solutions and no hope.
Thank you
For anyone having this problem, the issue was that I was sending the request inside the "params" instead of the url, weirdly enough, it didn't proccess it correctly, so make this small change:
const requestObservable = this.httpService.request<T>({
method,
url: `${process.env.BINANCE_API_URL}/${endpoint}?${query}&signature=${signature}`,
headers
}).pipe(
map(res => res.data)
);
And you are good to go
I am trying to fetch the customerId for myAPIClient using swift firebase and node.js. When I run my application it crashes and tells me that the customerId is nil. The reason that I am needing this customerId is in order to create the ephemeralKey.
The code that I use in node.js is,
exports.createEphemeralKey = functions.https.onRequest((req, res) => {
const stripe_version = req.body.api_version;
const customerId = req.body.customerId
if (!stripe_version) {
console.log('I did not see any api version')
res.status(400).end()
return;
}
stripe.ephemeralKeys.create(
{customer: customerId},
{stripe_version: apiVersion}
).then((key) => {
console.log("Ephemeral key: " + key)
res.status(200).json(key)
}).catch((err) => {
console.log('stripe version is ' + stripe_version + " and customer id is " + customerId + " for key: " + stripe_key + " and err is " + err.message )
res.status(500).json(err)
});
});
and the code that I am using inside of myApiClient is.
func createCustomerKey(withAPIVersion apiVersion: String, completion: #escaping STPJSONResponseCompletionBlock) {
let url = self.baseURL.appendingPathComponent("ephemeral_keys")
let defaults = UserDefaults.standard
let customerId = defaults.string(forKey: "customerId")
AF.request(url, method: .post, parameters: [
"api_version": apiVersion, "customer_id": customerId!
])
.validate(statusCode: 200..<300)
.responseJSON { responseJSON in
switch responseJSON.result {
case .success(let json):
completion(json as? [String: AnyObject], nil)
case .failure(let error):
completion(nil, error)
}
}
}
I have been stuck on creating this ephemeralKey for a while now and have asked other questions but have not gotten a real answer. Is there anything that I am missing? How can I actually access the customersId from stripe inside of my ios project.
You should first check that let customerId = defaults.string(forKey: "customerId") provides a valid customerId. You can do so by debugging your app and inspecting the value of customerId. Assuming that it is valid, I see a couple of things that may be causing issues for you:
The Alamofire library that you are using has 2 Parameter Encoders with different properties and options. You can see them here.
My hypothesis is that you are nor passing the customerId correclty to the body of the request. I suggest you start by trying the JSONParameterEncoder. Your request would be somehting like this:
AF.request(url, method: .post, parameters: [
"api_version": apiVersion, "customer_id": customerId!
],
encoder: JSONParameterEncoder.default)
It is possible that you also have to tweak the way your retrieve the customerID from the Cloud Function according to how you pass the info from the swift app. This docs may provide some insights on how to parse HTTP request info
I am trying to create an ephemeral key in my IOS app. I can successfully create a stripe customer that saves in my firebase console and on my stripe dashboard. However, when I try to create the ephemeral key, I am receiving the error in my ios console after trying to view the checkout controller.
'Could not parse the ephemeral key response following protocol STPCustomerEphemeralKeyProvider. Make sure your backend is sending the unmodified JSON of the ephemeral key to your app.
and on my firebase function logs I am seeing,
createEphemeralKey
Request has incorrect Content-Type.
createEphemeralKey
Invalid request, unable to process.
in my index.js file, the code that I am using is
exports.createEphemeralKey = functions.https.onCall(async(data, context) => {
var stripeVersion = data.api_version;
const customerId = data.customer_id;
return stripe.ephemeralKeys.create(
{customer: customerId},
{stripe_version: stripeVersion}
).then((key) => {
return key
}).catch((err) => {
console.log(err)
})
})
Below is how I create my stripe customer.
exports.createStripeCustomer = functions.auth.user().onCreate((user) => {
return stripe.customers.create({
email: user.email,
}).then((customer) => {
return admin.database().ref(`/stripe_customers/${user.uid}/customer_id`).set(customer.id);
});
});
and then myAPIClient looks like.
enum APIError: Error {
case unknown
var localizedDescription: String {
switch self {
case .unknown:
return "Unknown error"
}
}
}
static let sharedClient = MyAPIClient()
var baseURLString: String? = "https://myProject.cloudfunctions.net/"
var baseURL: URL {
if let urlString = self.baseURLString, let url = URL(string: urlString) {
return url
} else {
fatalError()
}
}
func createCustomerKey(withAPIVersion apiVersion: String, completion: #escaping STPJSONResponseCompletionBlock) {
let url = self.baseURL.appendingPathComponent("ephemeral_keys")
Alamofire.request(url, method: .post, parameters: [
"api_version": apiVersion,
])
.validate(statusCode: 200..<300)
.responseJSON { responseJSON in
switch responseJSON.result {
case .success(let json):
completion(json as? [String: AnyObject], nil)
case .failure(let error):
completion(nil, error)
}
}
}
On my checkOutVC, I have
var stripePublishableKey = "pk_test_testProjectKey"
var backendBaseURL: String? = "https://myProject.cloudfunctions.net"
let customerContext = STPCustomerContext(keyProvider: MyAPIClient())
init(price: Int, settings: Settings) {
if let stripePublishableKey = UserDefaults.standard.string(forKey: "StripePublishableKey") {
self.stripePublishableKey = stripePublishableKey
}
if let backendBaseURL = UserDefaults.standard.string(forKey: "StripeBackendBaseURL") {
self.backendBaseURL = backendBaseURL
}
let stripePublishableKey = self.stripePublishableKey
let backendBaseURL = self.backendBaseURL
assert(stripePublishableKey.hasPrefix("pk_"), "You must set your Stripe publishable key at the top of acceptWorker.swift to run this app.")
assert(backendBaseURL != nil, "You must set your backend base url at the top of acceptWorker.swift to run this app.")
Stripe.setDefaultPublishableKey(self.stripePublishableKey)
let config = STPPaymentConfiguration.shared()
config.appleMerchantIdentifier = self.appleMerchantID
config.companyName = self.companyName
config.requiredBillingAddressFields = settings.requiredBillingAddressFields
config.requiredShippingAddressFields = settings.requiredShippingAddressFields
config.shippingType = settings.shippingType
config.additionalPaymentOptions = settings.additionalPaymentOptions
config.cardScanningEnabled = true
self.country = settings.country
self.paymentCurrency = settings.currency
self.theme = settings.theme
MyAPIClient.sharedClient.baseURLString = self.backendBaseURL
let paymentContext = STPPaymentContext(customerContext: customerContext, configuration: config, theme: settings.theme)
self.paymentContext = STPPaymentContext(customerContext: customerContext)
super.init(nibName: nil, bundle: nil)
self.paymentContext.delegate = self
self.paymentContext.hostViewController = self
self.paymentContext.paymentAmount = 5000 // This is in cents, i.e. $50 USD
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
I apologize for the long lines of code but I am really running into a brick wall. Why isnt the backend creating the ephemeralKey for customers?
Two things are jumping out at me:
You’ve written a callable type function (using onCall) but you’re
trying to call it with a normal HTTP request. These functions need to
be called with Firebase’s client library
(https://firebase.google.com/docs/functions/callable#call_the_function).
This stack overflow answer provides some great links about this:
Firebase Cloud Function to delete user.
Your firebase function is parsing stripe_version and customer_id from
data, but your request is only sending api_version. Where in your
code are you sending stripe_version and customer_id?
I am working on entity and intents creation in my agent using v2 client library for node.js . And for that i am going through this sample which is on git. And it says something related to session id and context id. Can anyone explain me what is sessionId and contextId. And also provide me link where i can read those thing in details.
I am unable to create context by following those example. How can i create context while creating intent at the same time.
The following is code to create a context. You cannot create a context and an intent in a single API call, you first need to create the context and then create the intent that uses the context. The response to the create context API call will return a context ID you can use in your intent.
const dialogflow = require('dialogflow');
// Instantiates clients
const entityTypesClient = new dialogflow.EntityTypesClient();
// The path to the agent the created entity type belongs to.
const agentPath = entityTypesClient.projectAgentPath(projectId);
const createEntityTypeRequest = {
parent: agentPath,
entityType: {
displayName: displayName,
kind: kind,
},
};
entityTypesClient
.createEntityType(createEntityTypeRequest)
.then(responses => {
console.log(`Created ${responses[0].name} entity type`);
})
.catch(err => {
console.error('Failed to create size entity type:', err);
});
Source: https://github.com/googleapis/nodejs-dialogflow/blob/master/samples/resource.js
Contexts are very closely associated with SessionID. Say for eg, you have a chatbot that gets spun up on two computers serving two different user's. Each user will have a respective session_id (If you're coding in NODE, when a new user fires the chatbot, you need to ensure he/she will get a unique session_id).
Now, every unique session id will have unique contexts. From above example, let's say user 1 will initialize an intent that has input context named 'abc' with lifespan of 2 and user 2 will initialize another intent that has input context named 'xyz' with lifespan of 5, these respective contexts gets recorded against each of these user's individual session id's. You can programatically control (edit) contexts and its lifecycle. This is the biggest advantage of code facilitated Dialogflow as opposed to using GUI. Using services like Firebase, you can also preserve session id's and its associated contexts so, next time same user sign's in again, they can start from where they had last left.
I can share a snippet from one of my previous projects where I was managing contexts programatically. Initialization script is as follows:
/**
* #author Pruthvi Kumar
* #email pruthvikumar.123#gmail.com
* #create date 2018-08-15 04:42:22
* #modify date 2018-08-15 04:42:22
* #desc Dialogflow config for chatbot.
*/
const dialogflow_config = {
projectId: 'xxx',
sessionId: 'chatbot-session-id', //This is default assignment. This will hve to be overridden by sessionId as obtained from client in order to main context per sessionId.
languageCode: 'en-US'
};
exports.configStoreSingleton = (function () {
let instanceStacks;
let instanceSessionId;
let contextStack = {};
let intentsStack = {};
let successfulIntentResponseStack = {};
function init() {
contextStack[dialogflow_config['sessionId']] = [];
intentsStack[dialogflow_config['sessionId']] = [];
successfulIntentResponseStack[dialogflow_config['sessionId']] = [];
return {
contextStack: contextStack,
intentsStack: intentsStack,
successfulIntentResponseStack: successfulIntentResponseStack
};
}
return {
init: function () {
if (!instanceStacks || (instanceSessionId !== dialogflow_config['sessionId'] && (!intentsStack[dialogflow_config['sessionId']]))) {
console.log('[dialogflow_config]: Singleton is not instantiated previously or New userSession is triggered! Fresh instance stack will be provisioned');
instanceStacks = init();
instanceSessionId = dialogflow_config['sessionId'];
}
return instanceStacks;
}
};
})();
exports.updateSessionIdOfDialogflowConfig = function (sessionId) {
if (typeof (sessionId) === 'string') {
dialogflow_config['sessionId'] = sessionId;
return true;
} else {
console.warn('[dialogflow_config]: SessionId must be of type STRING!');
return;
}
};
exports.getDialogflowConfig = function () {
return dialogflow_config;
};
And then, to programmatically manage contexts:
/**
* #author Pruthvi Kumar
* #email pruthvikumar.123#gmail.com
* #create date 2018-08-15 04:37:15
* #modify date 2018-08-15 04:37:15
* #desc Operate on Dialogflow Contexts
*/
const dialogflow = require('dialogflow');
const dialogflowConfig = require('../modules/dialogflow_config');
const structjson = require('./dialogflow_structjson');
const util = require('util');
const contextsClient = new dialogflow.ContextsClient();
exports.setContextHistory = function (sessionId, intent_name, context_payload, preservedContext=false) {
/* maintain context stack per session */
/* context_payload = {input_contexts: [], output_contexts = []}
*/
const contextStack = dialogflowConfig.configStoreSingleton.init().contextStack;
if (intent_name) {
contextStack[sessionId].push({
intent: intent_name,
contexts: context_payload,
preserveContext: preservedContext
});
} else {
console.warn('[dialogflow_contexts]: Intent name is not provided OR Nothing in context_payload to add to history!');
}
};
exports.getContextHistory = function () {
const contextStack = dialogflowConfig.configStoreSingleton.init().contextStack;
return contextStack;
}
exports.preserveContext = function () {
const contextStack = dialogflowConfig.configStoreSingleton.init().contextStack;
//Traverse contextStack, get the last contexts.
let context_to_be_preserved = contextStack[dialogflowConfig.getDialogflowConfig()['sessionId']][contextStack[dialogflowConfig.getDialogflowConfig()['sessionId']].length - 1];
//console.log(`context to be preserved is: ${util.inspect(context_to_be_preserved)}`);
return context_to_be_preserved['contexts'].map((context, index) => {
let context_id = exports.getContextId(context);
return exports.updateContext(context_id, true)
});
}
From here, you can reference this github resource to build your own contexts - https://github.com/googleapis/nodejs-dialogflow/blob/master/samples/resource.js
Happy creating digital souls!
I've sign up to start using Twilio and I'm trying to setup the quickstart (https://www.twilio.com/docs/voice/client/javascript/quickstart) and it's almost working but incoming calls are not being received by:
Client code (used on browser after getTokenCapabilities):
Twilio.Device.incoming(function (conn) {
log('Incoming connection from ' + conn.parameters.From);
var archEnemyPhoneNumber = '+12093373517';
if (conn.parameters.From === archEnemyPhoneNumber) {
conn.reject();
log('It\'s your nemesis. Rejected call.');
} else {
// accept the incoming connection and start two-way audio
conn.accept();
}
});
Code on Twilio Function for voice calls (consoles are always printed and else condition is never called:
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
console.log('entrou aqui');
if(event.To) {
console.log('entrou ali');
// Wrap the phone number or client name in the appropriate TwiML verb
// if is a valid phone number
const attr = isAValidPhoneNumber(event.To) ? 'number' : 'client';
const dial = twiml.dial({
callerId: context.CALLER_ID,
});
dial[attr]({}, event.To);
} else {
twiml.say('Thanks for calling!');
}
console.log('callback');
callback(null, twiml);
};
/**
* Checks if the given value is valid as phone number
* #param {Number|String} number
* #return {Boolean}
*/
function isAValidPhoneNumber(number) {
return /^[\d\+\-\(\) ]+$/.test(number);
}
I've include my phone number as Verified Caller ID, got a number from Twilio and create the functions using template Twilio Client Quickstart.
On Twilio Client Quickstart, i've paste TwiML SID as TWIML_APP_SID and tried to use my phone number and the number from Twilio as CALLER_ID.
Also I've changed VOICE URL on TwiML configuration and changed the VOICE URL on phone number from twilio configuration.
Any ideas on what is missing or what is wrong? When I open on browser http://127.0.0.1:8080/, It's possible to make calls but I don't receive any call on browser when I call to twilio number.
In order to answer call, you need to have your token name identity in a tag in your VoiceResponse, here is an example.
exports.incomingVoiceResponse = function voiceResponse( to ) {
// Create a TwiML voice response
const twiml = new VoiceResponse();
// Wrap the phone number or client name in the appropriate TwiML verb
// if is a valid phone number
const attr = isAValidPhoneNumber(to) ? 'client' : 'number';
const dial = twiml.dial({
callerId: to,
});
dial[attr]({}, 'jesus');
console.log(twiml.toString())
return twiml.toString();
};
See the 'jesus' client tag I have put ? Here is the token generator side :
exports.tokenGenerator = function tokenGenerator() {
const identity = 'jesus';
const capability = new ClientCapability({
accountSid: config.accountSid,
authToken: config.authToken,
});
capability.addScope(new ClientCapability.IncomingClientScope(identity));
capability.addScope(new ClientCapability.OutgoingClientScope({
applicationSid: config.twimlAppSid,
clientName: identity,
}));
// Include identity and token in a JSON response
return {
identity: identity,
token: capability.toJwt(),
};
};
This works for me using the node quickstart as is and changing both of these functions.
However, don't forger to change the from 'number' to 'client' in function voiceResponse because it's an incoming call and not an outgoing.
const attr = isAValidPhoneNumber(to) ? 'client' : 'number';
instead of
const attr = isAValidPhoneNumber(to) ? 'number' : 'client';
Since the default nameGenerator from the client-quickstart-node from Twilio generates a random name, it isn't properly set when receiving incoming call.